mirror of
https://github.com/yattee/yattee.git
synced 2025-04-26 00:26:33 +00:00
Video details changes and channel sheet
This commit is contained in:
parent
5db74a3997
commit
67690bc435
@ -84,6 +84,9 @@ final class NavigationModel: ObservableObject {
|
|||||||
@Published var presentingAccounts = false
|
@Published var presentingAccounts = false
|
||||||
@Published var presentingWelcomeScreen = false
|
@Published var presentingWelcomeScreen = false
|
||||||
|
|
||||||
|
@Published var presentingChannelSheet = false
|
||||||
|
@Published var channelPresentedInSheet: Channel!
|
||||||
|
|
||||||
@Published var presentingShareSheet = false
|
@Published var presentingShareSheet = false
|
||||||
@Published var shareURL: URL?
|
@Published var shareURL: URL?
|
||||||
|
|
||||||
@ -103,7 +106,6 @@ final class NavigationModel: ObservableObject {
|
|||||||
|
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
let presentingPlayer = player.presentingPlayer
|
let presentingPlayer = player.presentingPlayer
|
||||||
player.hide()
|
|
||||||
presentingChannel = false
|
presentingChannel = false
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
@ -113,20 +115,30 @@ final class NavigationModel: ObservableObject {
|
|||||||
let recent = RecentItem(from: channel)
|
let recent = RecentItem(from: channel)
|
||||||
recents.add(RecentItem(from: channel))
|
recents.add(RecentItem(from: channel))
|
||||||
|
|
||||||
if navigationStyle == .sidebar {
|
let navigateToChannel = {
|
||||||
sidebarSectionChanged.toggle()
|
|
||||||
tabSelection = .recentlyOpened(recent.tag)
|
|
||||||
} else {
|
|
||||||
var delay = 0.0
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
if presentingPlayer { delay = 1.0 }
|
self.player.hide()
|
||||||
#endif
|
#endif
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
|
||||||
|
if navigationStyle == .sidebar {
|
||||||
|
self.sidebarSectionChanged.toggle()
|
||||||
|
self.tabSelection = .recentlyOpened(recent.tag)
|
||||||
|
} else {
|
||||||
withAnimation(Constants.overlayAnimation) {
|
withAnimation(Constants.overlayAnimation) {
|
||||||
self.presentingChannel = true
|
self.presentingChannel = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
if presentingPlayer {
|
||||||
|
presentChannelInSheet(channel)
|
||||||
|
} else {
|
||||||
|
navigateToChannel()
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
navigateToChannel()
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
func openChannelPlaylist(_ playlist: ChannelPlaylist, navigationStyle: NavigationStyle) {
|
func openChannelPlaylist(_ playlist: ChannelPlaylist, navigationStyle: NavigationStyle) {
|
||||||
@ -273,6 +285,11 @@ final class NavigationModel: ObservableObject {
|
|||||||
shareURL = url
|
shareURL = url
|
||||||
presentingShareSheet = true
|
presentingShareSheet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func presentChannelInSheet(_ channel: Channel) {
|
||||||
|
channelPresentedInSheet = channel
|
||||||
|
presentingChannelSheet = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typealias TabSelection = NavigationModel.TabSelection
|
typealias TabSelection = NavigationModel.TabSelection
|
||||||
|
@ -108,6 +108,7 @@ struct OpenVideosModel {
|
|||||||
)
|
)
|
||||||
|
|
||||||
WatchNextViewModel.shared.hide()
|
WatchNextViewModel.shared.hide()
|
||||||
|
NavigationModel.shared.presentingChannelSheet = false
|
||||||
|
|
||||||
if playbackMode == .playNow || playbackMode == .shuffleAll {
|
if playbackMode == .playNow || playbackMode == .shuffleAll {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
|
@ -335,6 +335,7 @@ final class PlayerModel: ObservableObject {
|
|||||||
videoBeingOpened = video
|
videoBeingOpened = video
|
||||||
|
|
||||||
WatchNextViewModel.shared.hide()
|
WatchNextViewModel.shared.hide()
|
||||||
|
navigation.presentingChannelSheet = false
|
||||||
|
|
||||||
var changeBackendHandler: (() -> Void)?
|
var changeBackendHandler: (() -> Void)?
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@ extension PlayerModel {
|
|||||||
|
|
||||||
func play(_ videos: [Video], shuffling: Bool = false) {
|
func play(_ videos: [Video], shuffling: Bool = false) {
|
||||||
WatchNextViewModel.shared.hide()
|
WatchNextViewModel.shared.hide()
|
||||||
|
navigation.presentingChannelSheet = false
|
||||||
|
|
||||||
playbackMode = shuffling ? .shuffle : .queue
|
playbackMode = shuffling ? .shuffle : .queue
|
||||||
|
|
||||||
videos.forEach { enqueueVideo($0, loadDetails: false) }
|
videos.forEach { enqueueVideo($0, loadDetails: false) }
|
||||||
@ -33,6 +35,8 @@ extension PlayerModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func playNow(_ video: Video, at time: CMTime? = nil) {
|
func playNow(_ video: Video, at time: CMTime? = nil) {
|
||||||
|
navigation.presentingChannelSheet = false
|
||||||
|
|
||||||
if playingInPictureInPicture, closePiPOnNavigation {
|
if playingInPictureInPicture, closePiPOnNavigation {
|
||||||
closePiP()
|
closePiP()
|
||||||
}
|
}
|
||||||
@ -56,6 +60,7 @@ extension PlayerModel {
|
|||||||
comments.reset()
|
comments.reset()
|
||||||
stream = nil
|
stream = nil
|
||||||
WatchNextViewModel.shared.hide()
|
WatchNextViewModel.shared.hide()
|
||||||
|
navigation.presentingChannelSheet = false
|
||||||
|
|
||||||
withAnimation {
|
withAnimation {
|
||||||
aspectRatio = VideoPlayerView.defaultAspectRatio
|
aspectRatio = VideoPlayerView.defaultAspectRatio
|
||||||
@ -176,6 +181,7 @@ extension PlayerModel {
|
|||||||
remove(newItem)
|
remove(newItem)
|
||||||
|
|
||||||
WatchNextViewModel.shared.hide()
|
WatchNextViewModel.shared.hide()
|
||||||
|
navigation.presentingChannelSheet = false
|
||||||
currentItem = newItem
|
currentItem = newItem
|
||||||
currentItem.playbackTime = time
|
currentItem.playbackTime = time
|
||||||
|
|
||||||
@ -219,9 +225,12 @@ extension PlayerModel {
|
|||||||
let item = PlayerQueueItem(video, playbackTime: atTime)
|
let item = PlayerQueueItem(video, playbackTime: atTime)
|
||||||
|
|
||||||
if play {
|
if play {
|
||||||
|
navigation.presentingChannelSheet = false
|
||||||
|
|
||||||
withAnimation {
|
withAnimation {
|
||||||
aspectRatio = VideoPlayerView.defaultAspectRatio
|
aspectRatio = VideoPlayerView.defaultAspectRatio
|
||||||
WatchNextViewModel.shared.hide()
|
WatchNextViewModel.shared.hide()
|
||||||
|
navigation.presentingChannelSheet = false
|
||||||
currentItem = item
|
currentItem = item
|
||||||
}
|
}
|
||||||
videoBeingOpened = video
|
videoBeingOpened = video
|
||||||
|
@ -6,6 +6,7 @@ import SwiftUI
|
|||||||
struct ChannelVideosView: View {
|
struct ChannelVideosView: View {
|
||||||
var channel: Channel?
|
var channel: Channel?
|
||||||
var showCloseButton = false
|
var showCloseButton = false
|
||||||
|
var inNavigationView = true
|
||||||
|
|
||||||
@State private var presentingShareSheet = false
|
@State private var presentingShareSheet = false
|
||||||
@State private var shareURL: URL?
|
@State private var shareURL: URL?
|
||||||
@ -119,24 +120,28 @@ struct ChannelVideosView: View {
|
|||||||
Button {
|
Button {
|
||||||
withAnimation(Constants.overlayAnimation) {
|
withAnimation(Constants.overlayAnimation) {
|
||||||
navigation.presentingChannel = false
|
navigation.presentingChannel = false
|
||||||
|
navigation.presentingChannelSheet = false
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("Close", systemImage: "xmark")
|
Label("Close", systemImage: "xmark")
|
||||||
}
|
}
|
||||||
|
#if !os(macOS)
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if !os(iOS)
|
#if os(macOS)
|
||||||
ToolbarItem(placement: .navigation) {
|
ToolbarItem(placement: .navigation) {
|
||||||
thumbnail
|
thumbnail
|
||||||
}
|
}
|
||||||
ToolbarItem {
|
ToolbarItemGroup {
|
||||||
|
if !inNavigationView {
|
||||||
|
Text(navigationTitle)
|
||||||
|
.fontWeight(.bold)
|
||||||
|
}
|
||||||
|
|
||||||
ListingStyleButtons(listingStyle: $channelPlaylistListingStyle)
|
ListingStyleButtons(listingStyle: $channelPlaylistListingStyle)
|
||||||
}
|
|
||||||
ToolbarItem {
|
|
||||||
HideShortsButtons(hide: $hideShorts)
|
HideShortsButtons(hide: $hideShorts)
|
||||||
}
|
|
||||||
ToolbarItem {
|
|
||||||
contentTypePicker
|
contentTypePicker
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,10 +165,12 @@ struct ChannelVideosView: View {
|
|||||||
|
|
||||||
ToolbarItem {
|
ToolbarItem {
|
||||||
favoriteButton
|
favoriteButton
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolbarItem {
|
ToolbarItem {
|
||||||
toggleWatchedButton
|
toggleWatchedButton
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -234,14 +241,14 @@ struct ChannelVideosView: View {
|
|||||||
Group {
|
Group {
|
||||||
if let subscribers = store.item?.channel?.subscriptionsString {
|
if let subscribers = store.item?.channel?.subscriptionsString {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Text(subscribers)
|
|
||||||
Image(systemName: "person.2.fill")
|
Image(systemName: "person.2.fill")
|
||||||
|
Text(subscribers)
|
||||||
}
|
}
|
||||||
} else if store.item.isNil {
|
} else if store.item.isNil {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
|
Image(systemName: "person.2.fill")
|
||||||
Text("1234")
|
Text("1234")
|
||||||
.redacted(reason: .placeholder)
|
.redacted(reason: .placeholder)
|
||||||
Image(systemName: "person.2.fill")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -252,10 +259,10 @@ struct ChannelVideosView: View {
|
|||||||
var viewsLabel: some View {
|
var viewsLabel: some View {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
if let views = store.item?.channel?.totalViewsString {
|
if let views = store.item?.channel?.totalViewsString {
|
||||||
Text(views)
|
|
||||||
|
|
||||||
Image(systemName: "eye.fill")
|
Image(systemName: "eye.fill")
|
||||||
.imageScale(.small)
|
.imageScale(.small)
|
||||||
|
|
||||||
|
Text(views)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
@ -328,6 +335,7 @@ struct ChannelVideosView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.labelsHidden()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func typeAvailable(_ type: Channel.ContentType) -> Bool {
|
private func typeAvailable(_ type: Channel.ContentType) -> Bool {
|
||||||
@ -463,7 +471,7 @@ struct ChannelVideosView: View {
|
|||||||
struct ChannelVideosView_Previews: PreviewProvider {
|
struct ChannelVideosView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
ChannelVideosView(channel: Video.fixture.channel)
|
ChannelVideosView(channel: Video.fixture.channel, showCloseButton: true, inNavigationView: false)
|
||||||
.environment(\.navigationStyle, .sidebar)
|
.environment(\.navigationStyle, .sidebar)
|
||||||
#else
|
#else
|
||||||
NavigationView {
|
NavigationView {
|
||||||
|
@ -241,6 +241,7 @@ extension Defaults.Keys {
|
|||||||
static let openWatchNextOnFinishedWatchingDelay = Key<String>("openWatchNextOnFinishedWatchingDelay", default: "5")
|
static let openWatchNextOnFinishedWatchingDelay = Key<String>("openWatchNextOnFinishedWatchingDelay", default: "5")
|
||||||
|
|
||||||
static let hideShorts = Key<Bool>("hideShorts", default: false)
|
static let hideShorts = Key<Bool>("hideShorts", default: false)
|
||||||
|
static let showInspector = Key<ShowInspectorSetting>("showInspector", default: .onlyLocal)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ResolutionSetting: String, CaseIterable, Defaults.Serializable {
|
enum ResolutionSetting: String, CaseIterable, Defaults.Serializable {
|
||||||
|
@ -120,6 +120,15 @@ struct ContentView: View {
|
|||||||
OpenVideosView()
|
OpenVideosView()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
#if !os(macOS)
|
||||||
|
.background(
|
||||||
|
EmptyView().sheet(isPresented: $navigation.presentingChannelSheet) {
|
||||||
|
NavigationView {
|
||||||
|
ChannelVideosView(channel: navigation.channelPresentedInSheet, showCloseButton: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
#endif
|
||||||
.alert(isPresented: $navigation.presentingAlert) { navigation.alert }
|
.alert(isPresented: $navigation.presentingAlert) { navigation.alert }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ struct VideoDetailsOverlay: View {
|
|||||||
@ObservedObject private var controls = PlayerControlsModel.shared
|
@ObservedObject private var controls = PlayerControlsModel.shared
|
||||||
|
|
||||||
var body: some View {
|
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))
|
.clipShape(RoundedRectangle(cornerRadius: 4))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,9 +5,9 @@ struct RelatedView: View {
|
|||||||
@ObservedObject private var player = PlayerModel.shared
|
@ObservedObject private var player = PlayerModel.shared
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
LazyVStack {
|
||||||
if let related = player.currentVideo?.related {
|
if let related = player.videoForDisplay?.related {
|
||||||
Section(header: Text("Related")) {
|
Section(header: header) {
|
||||||
ForEach(related) { video in
|
ForEach(related) { video in
|
||||||
PlayerQueueRow(item: PlayerQueueItem(video))
|
PlayerQueueRow(item: PlayerQueueItem(video))
|
||||||
.listRowBackground(Color.clear)
|
.listRowBackground(Color.clear)
|
||||||
@ -34,6 +34,15 @@ struct RelatedView: View {
|
|||||||
.listStyle(.plain)
|
.listStyle(.plain)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var header: some View {
|
||||||
|
Text("Related")
|
||||||
|
#if !os(macOS)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RelatedView_Previews: PreviewProvider {
|
struct RelatedView_Previews: PreviewProvider {
|
||||||
|
@ -25,7 +25,6 @@ struct CommentsView: View {
|
|||||||
.borderBottom(height: comment != last ? 0.5 : 0, color: Color("ControlsBorderColor"))
|
.borderBottom(height: comment != last ? 0.5 : 0, color: Color("ControlsBorderColor"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.top, 55)
|
|
||||||
|
|
||||||
if embedInScrollView {
|
if embedInScrollView {
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
|
@ -6,7 +6,7 @@ struct InspectorView: View {
|
|||||||
@ObservedObject private var player = PlayerModel.shared
|
@ObservedObject private var player = PlayerModel.shared
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
Section(header: header) {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
if let video {
|
if let video {
|
||||||
VStack(spacing: 4) {
|
VStack(spacing: 4) {
|
||||||
@ -53,10 +53,14 @@ struct InspectorView: View {
|
|||||||
NoCommentsView(text: "Not playing", systemImage: "stop.circle.fill")
|
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 {
|
@ViewBuilder func videoDetailGroupHeading(_ heading: String, image systemName: String? = nil) -> some View {
|
||||||
|
@ -13,7 +13,7 @@ struct PlayerQueueView: View {
|
|||||||
@Default(.saveHistory) private var saveHistory
|
@Default(.saveHistory) private var saveHistory
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
Group {
|
||||||
Group {
|
Group {
|
||||||
if player.playbackMode == .related {
|
if player.playbackMode == .related {
|
||||||
autoplaying
|
autoplaying
|
||||||
@ -34,15 +34,6 @@ struct PlayerQueueView: View {
|
|||||||
.listRowSeparator(false)
|
.listRowSeparator(false)
|
||||||
}
|
}
|
||||||
.environment(\.inNavigationView, 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 {
|
@ViewBuilder var autoplaying: some View {
|
||||||
@ -65,6 +56,8 @@ struct PlayerQueueView: View {
|
|||||||
var autoplayingHeader: some View {
|
var autoplayingHeader: some View {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Autoplaying Next")
|
Text("Autoplaying Next")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.font(.caption)
|
||||||
Spacer()
|
Spacer()
|
||||||
Button {
|
Button {
|
||||||
player.setRelatedAutoplayItem()
|
player.setRelatedAutoplayItem()
|
||||||
@ -78,7 +71,7 @@ struct PlayerQueueView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var playingNext: some View {
|
var playingNext: some View {
|
||||||
Section(header: Text("Queue")) {
|
Section(header: queueHeader) {
|
||||||
if player.queue.isEmpty {
|
if player.queue.isEmpty {
|
||||||
Text("Queue is empty")
|
Text("Queue is empty")
|
||||||
.foregroundColor(.secondary)
|
.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] {
|
private var visibleWatches: [Watch] {
|
||||||
watches.filter { $0.videoID != player.currentVideo?.videoID }
|
watches.filter { $0.videoID != player.currentVideo?.videoID }
|
||||||
}
|
}
|
||||||
|
@ -4,22 +4,147 @@ import SDWebImageSwiftUI
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct VideoDetails: View {
|
struct VideoDetails: View {
|
||||||
enum DetailsPage: String, CaseIterable, Defaults.Serializable {
|
struct TitleView: View {
|
||||||
case info, comments, chapters, inspector
|
@ObservedObject private var model = PlayerModel.shared
|
||||||
|
@State private var titleSize = CGSize.zero
|
||||||
|
|
||||||
var systemImageName: String {
|
var video: Video? { model.videoForDisplay }
|
||||||
switch self {
|
|
||||||
case .info:
|
var body: some View {
|
||||||
return "info.circle"
|
HStack(spacing: 0) {
|
||||||
case .inspector:
|
Text(model.videoForDisplay?.displayTitle ?? "Not playing")
|
||||||
return "wand.and.stars"
|
.font(.title3.bold())
|
||||||
case .comments:
|
.lineLimit(4)
|
||||||
return "text.bubble"
|
}
|
||||||
case .chapters:
|
.padding(.vertical, 4)
|
||||||
return "bookmark"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
var title: String {
|
||||||
rawValue.capitalized.localized()
|
rawValue.capitalized.localized()
|
||||||
}
|
}
|
||||||
@ -28,7 +153,7 @@ struct VideoDetails: View {
|
|||||||
var video: Video?
|
var video: Video?
|
||||||
|
|
||||||
@Binding var fullScreen: Bool
|
@Binding var fullScreen: Bool
|
||||||
var bottomPadding = false
|
@Binding var sidebarQueue: Bool
|
||||||
|
|
||||||
@State private var detailsSize = CGSize.zero
|
@State private var detailsSize = CGSize.zero
|
||||||
@State private var detailsVisibility = Constants.detailsVisibility
|
@State private var detailsVisibility = Constants.detailsVisibility
|
||||||
@ -49,22 +174,40 @@ struct VideoDetails: View {
|
|||||||
|
|
||||||
@Default(.enableReturnYouTubeDislike) private var enableReturnYouTubeDislike
|
@Default(.enableReturnYouTubeDislike) private var enableReturnYouTubeDislike
|
||||||
@Default(.playerSidebar) private var playerSidebar
|
@Default(.playerSidebar) private var playerSidebar
|
||||||
|
@Default(.showInspector) private var showInspector
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
ControlsBar(
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
fullScreen: $fullScreen,
|
TitleView()
|
||||||
expansionState: .constant(.full),
|
if video != nil, !video!.isLocal {
|
||||||
presentingControls: false,
|
ChannelView()
|
||||||
backgroundEnabled: false,
|
.layoutPriority(1)
|
||||||
borderTop: false,
|
.padding(.bottom, 6)
|
||||||
detailsTogglePlayer: false,
|
}
|
||||||
detailsToggleFullScreen: true
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.padding(.horizontal, 16)
|
||||||
|
#if !os(tvOS)
|
||||||
|
.tapRecognizer(
|
||||||
|
tapSensitivity: 0.2,
|
||||||
|
doubleTapAction: {
|
||||||
|
withAnimation(.default) {
|
||||||
|
fullScreen.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.animation(nil, value: player.currentItem)
|
#endif
|
||||||
|
|
||||||
VideoActions(video: player.videoForDisplay)
|
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)
|
.animation(nil, value: player.currentItem)
|
||||||
|
.frame(minWidth: 0, maxWidth: .infinity)
|
||||||
|
|
||||||
pageView
|
pageView
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@ -100,103 +243,76 @@ struct VideoDetails: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder var pageMenu: some 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) {
|
Picker("Page", selection: $page) {
|
||||||
ForEach(DetailsPage.allCases.filter { pageAvailable($0) }, id: \.rawValue) { page in
|
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 {
|
func pageAvailable(_ page: DetailsPage) -> Bool {
|
||||||
guard let video else { return false }
|
guard let video else { return false }
|
||||||
|
|
||||||
switch page {
|
switch page {
|
||||||
case .inspector:
|
case .queue:
|
||||||
return true
|
return !player.queue.isEmpty
|
||||||
default:
|
default:
|
||||||
return !video.isLocal
|
return !video.isLocal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var pageView: some View {
|
var pageView: some View {
|
||||||
ZStack(alignment: .topLeading) {
|
ScrollViewReader { proxy in
|
||||||
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
|
LazyVStack {
|
||||||
|
pageMenu
|
||||||
|
.id("top")
|
||||||
|
.padding(5)
|
||||||
|
|
||||||
switch page {
|
switch page {
|
||||||
case .info:
|
case .info:
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
Group {
|
||||||
if let video {
|
if let video {
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
HStack {
|
|
||||||
videoProperties
|
|
||||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
|
||||||
}
|
|
||||||
.padding(.bottom, 12)
|
|
||||||
|
|
||||||
if !player.videoBeingOpened.isNil && (video.description.isNil || video.description!.isEmpty) {
|
if !player.videoBeingOpened.isNil && (video.description.isNil || video.description!.isEmpty) {
|
||||||
VStack {
|
VStack {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.progressViewStyle(.circular)
|
.progressViewStyle(.circular)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
} else if video.description != nil, !video.description!.isEmpty {
|
} else if let description = video.description, !description.isEmpty {
|
||||||
VideoDescription(video: video, detailsSize: detailsSize)
|
VideoDescription(video: video, detailsSize: detailsSize)
|
||||||
#if os(iOS)
|
|
||||||
.padding(.bottom, player.playingFullScreen ? 10 : SafeArea.insets.bottom)
|
|
||||||
#endif
|
|
||||||
} else if !video.isLocal {
|
} else if !video.isLocal {
|
||||||
Text("No description")
|
Text("No description")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if video.isLocal || showInspector == .always {
|
||||||
|
InspectorView(video: player.videoForDisplay)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sidebarQueue,
|
||||||
|
!(player.videoForDisplay?.related.isEmpty ?? true)
|
||||||
|
{
|
||||||
|
RelatedView()
|
||||||
|
.padding(.top, 20)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding(.top, 18)
|
|
||||||
.padding(.bottom, 60)
|
.padding(.bottom, 60)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onChange(of: player.currentVideo?.cacheKey) { _ in
|
||||||
|
proxy.scrollTo("top")
|
||||||
|
page = .info
|
||||||
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if video != nil, !pageAvailable(page) {
|
if video != nil, !pageAvailable(page) {
|
||||||
page = .inspector
|
page = .info
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
|
||||||
.onAppear {
|
|
||||||
if fullScreen {
|
|
||||||
if let video, video.isLocal {
|
|
||||||
page = .inspector
|
|
||||||
}
|
|
||||||
detailsVisibility = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Delay.by(0.4) { withAnimation(.easeIn(duration: 0.25)) { self.detailsVisibility = true } }
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
.animation(nil, value: player.currentItem)
|
.animation(nil, value: player.currentItem)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
@ -204,106 +320,35 @@ struct VideoDetails: View {
|
|||||||
.frame(maxWidth: YatteeApp.isForPreviews ? .infinity : maxWidth)
|
.frame(maxWidth: YatteeApp.isForPreviews ? .infinity : maxWidth)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
case .inspector:
|
case .queue:
|
||||||
InspectorView(video: video)
|
PlayerQueueView(sidebarQueue: false)
|
||||||
|
.padding(.horizontal)
|
||||||
case .chapters:
|
|
||||||
ChaptersView()
|
|
||||||
|
|
||||||
case .comments:
|
case .comments:
|
||||||
CommentsView(embedInScrollView: true)
|
CommentsView(embedInScrollView: false)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
comments.loadIfNeeded()
|
comments.loadIfNeeded()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
pageMenu
|
}
|
||||||
.font(.headline)
|
}
|
||||||
.foregroundColor(.accentColor)
|
#if os(iOS)
|
||||||
.zIndex(1)
|
.onAppear {
|
||||||
|
if fullScreen {
|
||||||
#if !os(tvOS)
|
if let video, video.isLocal {
|
||||||
if #available(iOS 16, macOS 13, *) {
|
page = .info
|
||||||
Rectangle()
|
}
|
||||||
.fill(
|
detailsVisibility = true
|
||||||
LinearGradient(
|
return
|
||||||
gradient: .init(colors: [fadePlaceholderStartColor, .clear]),
|
}
|
||||||
startPoint: .top,
|
Delay.by(0.8) { withAnimation(.easeIn(duration: 0.25)) { self.detailsVisibility = true } }
|
||||||
endPoint: .bottom
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.zIndex(0)
|
|
||||||
.frame(maxHeight: 22)
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var fadePlaceholderStartColor: Color {
|
.onChange(of: player.queue) { _ in
|
||||||
#if os(macOS)
|
if video != nil, !pageAvailable(page) {
|
||||||
.secondaryBackground
|
page = .info
|
||||||
#elseif os(iOS)
|
|
||||||
.background
|
|
||||||
#else
|
|
||||||
.clear
|
|
||||||
#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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -311,6 +356,6 @@ struct VideoDetails: View {
|
|||||||
|
|
||||||
struct VideoDetails_Previews: PreviewProvider {
|
struct VideoDetails_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
VideoDetails(video: .fixture, fullScreen: .constant(false))
|
VideoDetails(video: .fixture, fullScreen: .constant(false), sidebarQueue: .constant(false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,6 +92,14 @@ struct VideoPlayerView: View {
|
|||||||
.onChange(of: playerSidebar) { _ in
|
.onChange(of: playerSidebar) { _ in
|
||||||
updateSidebarQueue()
|
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 {
|
var videoPlayer: some View {
|
||||||
@ -323,7 +331,7 @@ struct VideoPlayerView: View {
|
|||||||
VideoDetails(
|
VideoDetails(
|
||||||
video: player.videoForDisplay,
|
video: player.videoForDisplay,
|
||||||
fullScreen: $fullScreenDetails,
|
fullScreen: $fullScreenDetails,
|
||||||
bottomPadding: detailsNeedBottomPadding
|
sidebarQueue: $sidebarQueue
|
||||||
)
|
)
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.ignoresSafeArea(.all, edges: .bottom)
|
.ignoresSafeArea(.all, edges: .bottom)
|
||||||
@ -386,15 +394,28 @@ struct VideoPlayerView: View {
|
|||||||
if !fullScreenPlayer {
|
if !fullScreenPlayer {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
if sidebarQueue {
|
if sidebarQueue {
|
||||||
|
List {
|
||||||
PlayerQueueView(sidebarQueue: true)
|
PlayerQueueView(sidebarQueue: true)
|
||||||
|
}
|
||||||
|
#if os(macOS)
|
||||||
|
.listStyle(.inset)
|
||||||
|
#elseif os(iOS)
|
||||||
|
.listStyle(.grouped)
|
||||||
|
.backport
|
||||||
|
.scrollContentBackground(false)
|
||||||
|
#else
|
||||||
|
.listStyle(.plain)
|
||||||
|
#endif
|
||||||
.frame(maxWidth: 350)
|
.frame(maxWidth: 350)
|
||||||
.background(colorScheme == .dark ? Color.black : Color.white)
|
.background(colorScheme == .dark ? Color.black : Color.white)
|
||||||
.transition(.move(edge: .bottom))
|
.transition(.move(edge: .bottom))
|
||||||
}
|
}
|
||||||
#elseif os(macOS)
|
#elseif os(macOS)
|
||||||
if Defaults[.playerSidebar] != .never {
|
if Defaults[.playerSidebar] != .never {
|
||||||
|
List {
|
||||||
PlayerQueueView(sidebarQueue: true)
|
PlayerQueueView(sidebarQueue: true)
|
||||||
.frame(width: 350)
|
}
|
||||||
|
.frame(maxWidth: 350)
|
||||||
.background(colorScheme == .dark ? Color.black : Color.white)
|
.background(colorScheme == .dark ? Color.black : Color.white)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -415,14 +436,6 @@ struct VideoPlayerView: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
var detailsNeedBottomPadding: Bool {
|
|
||||||
#if os(iOS)
|
|
||||||
return true
|
|
||||||
#else
|
|
||||||
return false
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
var fullScreenPlayer: Bool {
|
var fullScreenPlayer: Bool {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
player.playingFullScreen || verticalSizeClass == .compact
|
player.playingFullScreen || verticalSizeClass == .compact
|
||||||
|
@ -27,6 +27,7 @@ struct PlayerSettings: View {
|
|||||||
@Default(.openWatchNextOnClose) private var openWatchNextOnClose
|
@Default(.openWatchNextOnClose) private var openWatchNextOnClose
|
||||||
@Default(.openWatchNextOnFinishedWatching) private var openWatchNextOnFinishedWatching
|
@Default(.openWatchNextOnFinishedWatching) private var openWatchNextOnFinishedWatching
|
||||||
@Default(.openWatchNextOnFinishedWatchingDelay) private var openWatchNextOnFinishedWatchingDelay
|
@Default(.openWatchNextOnFinishedWatchingDelay) private var openWatchNextOnFinishedWatchingDelay
|
||||||
|
@Default(.showInspector) private var showInspector
|
||||||
|
|
||||||
@ObservedObject private var accounts = AccountsModel.shared
|
@ObservedObject private var accounts = AccountsModel.shared
|
||||||
|
|
||||||
@ -68,6 +69,12 @@ struct PlayerSettings: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !os(tvOS)
|
||||||
|
Section(header: SettingsHeader(text: "Inspector".localized())) {
|
||||||
|
inspectorVisibilityPicker
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
Section(header: SettingsHeader(text: "Watch Next")) {
|
Section(header: SettingsHeader(text: "Watch Next")) {
|
||||||
openWatchNextOnFinishedWatchingToggle
|
openWatchNextOnFinishedWatchingToggle
|
||||||
openWatchNextOnFinishedWatchingDelayTextField
|
openWatchNextOnFinishedWatchingDelayTextField
|
||||||
@ -235,6 +242,14 @@ struct PlayerSettings: View {
|
|||||||
Toggle("Close PiP and open player when application enters foreground", isOn: $closePiPAndOpenPlayerOnEnteringForeground)
|
Toggle("Close PiP and open player when application enters foreground", isOn: $closePiPAndOpenPlayerOnEnteringForeground)
|
||||||
}
|
}
|
||||||
#endif
|
#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 {
|
struct PlayerSettings_Previews: PreviewProvider {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user