Details panels in controls

This commit is contained in:
Arkadiusz Fal 2022-07-10 19:51:46 +02:00
parent db46289813
commit f0b8e7f655
15 changed files with 203 additions and 123 deletions

View File

@ -0,0 +1,12 @@
import Foundation
import SwiftUI
extension Backport where Content: View {
@ViewBuilder func scrollContentBackground(_ visibility: Bool) -> some View {
if #available(iOS 16.0, macOS 13.0, tvOS 16.0, *) {
content.scrollContentBackground(visibility ? .visible : .hidden)
} else {
content
}
}
}

View File

@ -96,23 +96,19 @@ final class NavigationModel: ObservableObject {
player.hide() player.hide()
navigation.presentingChannel = false navigation.presentingChannel = false
let recent = RecentItem(from: channel)
#if os(macOS) #if os(macOS)
Windows.main.open() Windows.main.open()
#else
player.hide()
#endif #endif
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { let recent = RecentItem(from: channel)
recents.add(recent) recents.add(RecentItem(from: channel))
if navigationStyle == .sidebar { if navigationStyle == .sidebar {
navigation.sidebarSectionChanged.toggle() navigation.sidebarSectionChanged.toggle()
navigation.tabSelection = .recentlyOpened(recent.tag) navigation.tabSelection = .recentlyOpened(recent.tag)
} else { } else {
withAnimation { withAnimation {
navigation.presentingChannel = true navigation.presentingChannel = true
}
} }
} }
} }

View File

@ -8,6 +8,7 @@ final class PlayerControlsModel: ObservableObject {
@Published var isPlaying = true @Published var isPlaying = true
@Published var presentingControls = false { didSet { handlePresentationChange() } } @Published var presentingControls = false { didSet { handlePresentationChange() } }
@Published var presentingControlsOverlay = false { didSet { handleOverlayPresentationChange() } } @Published var presentingControlsOverlay = false { didSet { handleOverlayPresentationChange() } }
@Published var presentingDetailsOverlay = false
@Published var timer: Timer? @Published var timer: Timer?
#if os(tvOS) #if os(tvOS)
@ -21,6 +22,7 @@ final class PlayerControlsModel: ObservableObject {
isPlaying: Bool = true, isPlaying: Bool = true,
presentingControls: Bool = false, presentingControls: Bool = false,
presentingControlsOverlay: Bool = false, presentingControlsOverlay: Bool = false,
presentingDetailsOverlay: Bool = false,
timer: Timer? = nil, timer: Timer? = nil,
player: PlayerModel? = nil player: PlayerModel? = nil
) { ) {
@ -28,20 +30,22 @@ final class PlayerControlsModel: ObservableObject {
self.isPlaying = isPlaying self.isPlaying = isPlaying
self.presentingControls = presentingControls self.presentingControls = presentingControls
self.presentingControlsOverlay = presentingControlsOverlay self.presentingControlsOverlay = presentingControlsOverlay
self.presentingDetailsOverlay = presentingDetailsOverlay
self.timer = timer self.timer = timer
self.player = player self.player = player
} }
func handlePresentationChange() { func handlePresentationChange() {
if presentingControls { DispatchQueue.main.async { [weak self] in
DispatchQueue.main.async { [weak self] in guard let self = self else { return }
self?.player?.backend.startControlsUpdates() if self.presentingControls {
self?.resetTimer() self.player?.backend.startControlsUpdates()
self.resetTimer()
} else {
self.player?.backend.stopControlsUpdates()
self.timer?.invalidate()
self.timer = nil
} }
} else {
player?.backend.stopControlsUpdates()
timer?.invalidate()
timer = nil
} }
} }
@ -54,6 +58,15 @@ final class PlayerControlsModel: ObservableObject {
} }
} }
var presentingOverlays: Bool {
presentingDetailsOverlay || presentingControlsOverlay
}
func hideOverlays() {
presentingDetailsOverlay = false
presentingControlsOverlay = false
}
func show() { func show() {
guard !(player?.currentItem.isNil ?? true) else { guard !(player?.currentItem.isNil ?? true) else {
return return

View File

@ -98,7 +98,13 @@ final class PlayerModel: ObservableObject {
var context: NSManagedObjectContext = PersistenceController.shared.container.viewContext var context: NSManagedObjectContext = PersistenceController.shared.container.viewContext
var backgroundContext = PersistenceController.shared.container.newBackgroundContext() var backgroundContext = PersistenceController.shared.container.newBackgroundContext()
@Published var playingFullScreen = false #if os(tvOS)
static let fullScreenIsDefault = true
#else
static let fullScreenIsDefault = false
#endif
@Published var playingFullScreen = PlayerModel.fullScreenIsDefault
@Published var playingInPictureInPicture = false @Published var playingInPictureInPicture = false
var pipController: AVPictureInPictureController? var pipController: AVPictureInPictureController?
var pipDelegate = PiPDelegate() var pipDelegate = PiPDelegate()

View File

@ -142,7 +142,7 @@ struct ContentView: View {
.environmentObject(recents) .environmentObject(recents)
.environmentObject(subscriptions) .environmentObject(subscriptions)
.environmentObject(thumbnailsModel) .environmentObject(thumbnailsModel)
.environment(\.navigationStyle, .sidebar) .environment(\.navigationStyle, navigationStyle)
} }
} }

View File

@ -29,7 +29,7 @@ struct PlayerControls: View {
} }
var body: some View { var body: some View {
ZStack(alignment: .topTrailing) { ZStack(alignment: .topLeading) {
VStack { VStack {
ZStack(alignment: .center) { ZStack(alignment: .center) {
OpeningStream() OpeningStream()
@ -39,19 +39,20 @@ struct PlayerControls: View {
VStack(spacing: 4) { VStack(spacing: 4) {
buttonsBar buttonsBar
if let video = player.currentVideo, player.playingFullScreen { HStack {
VStack(alignment: .leading, spacing: 2) { if !player.currentVideo.isNil, player.playingFullScreen {
Text(video.title) Button {
.font(.caption.bold()) withAnimation(Self.animation) {
model.presentingDetailsOverlay = true
Text(video.author) }
.font(.caption) } label: {
.foregroundColor(.secondary) ControlsBar(fullScreen: $model.presentingDetailsOverlay, presentingControls: false, detailsTogglePlayer: false, detailsToggleFullScreen: false)
.clipShape(RoundedRectangle(cornerRadius: 4))
.frame(maxWidth: 300, alignment: .leading)
}
.buttonStyle(.plain)
} }
.padding(4) Spacer()
.modifier(ControlBackgroundModifier())
.clipShape(RoundedRectangle(cornerRadius: 2))
.frame(maxWidth: .infinity, alignment: .leading)
} }
Spacer() Spacer()
@ -69,9 +70,6 @@ struct PlayerControls: View {
.offset(y: -25) .offset(y: -25)
.zIndex(1) .zIndex(1)
} }
#if os(tvOS)
.offset(y: -50)
#endif
.frame(maxWidth: 500) .frame(maxWidth: 500)
.padding(.bottom, 2) .padding(.bottom, 2)
} }
@ -79,7 +77,7 @@ struct PlayerControls: View {
.padding(.top, 2) .padding(.top, 2)
.padding(.horizontal, 2) .padding(.horizontal, 2)
} }
.opacity(model.presentingControlsOverlay ? 1 : model.presentingControls ? 1 : 0) .opacity(model.presentingOverlays ? 0 : model.presentingControls ? 1 : 0)
} }
} }
#if os(tvOS) #if os(tvOS)
@ -99,11 +97,16 @@ struct PlayerControls: View {
ControlsOverlay() ControlsOverlay()
.frame(height: overlayHeight) .frame(height: overlayHeight)
.padding() .padding()
.modifier(ControlBackgroundModifier(enabled: true)) .modifier(ControlBackgroundModifier())
.clipShape(RoundedRectangle(cornerRadius: 4)) .clipShape(RoundedRectangle(cornerRadius: 4))
.offset(x: -2, y: 40)
.opacity(model.presentingControlsOverlay ? 1 : 0) .opacity(model.presentingControlsOverlay ? 1 : 0)
VideoDetailsOverlay()
.frame(maxWidth: detailsWidth, maxHeight: 450)
.modifier(ControlBackgroundModifier())
.clipShape(RoundedRectangle(cornerRadius: 4))
.opacity(model.presentingDetailsOverlay ? 1 : 0)
Button { Button {
player.restoreLastSkippedSegment() player.restoreLastSkippedSegment()
} label: { } label: {
@ -124,13 +127,18 @@ struct PlayerControls: View {
.offset(x: -2, y: -2) .offset(x: -2, y: -2)
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.opacity(model.presentingControls ? 0 : player.lastSkipped.isNil ? 0 : 1) .opacity(model.presentingControls || model.presentingOverlays ? 0 : player.lastSkipped.isNil ? 0 : 1)
} }
} }
var overlayHeight: Double { var overlayHeight: Double {
guard let player = player, player.playerSize.height.isFinite else { return 0 } guard let player = player, player.playerSize.height.isFinite else { return 0 }
return [0, [player.playerSize.height - 80, 140].min()!].max()! return [0, [player.playerSize.height - 40, 140].min()!].max()!
}
var detailsWidth: Double {
guard let player = player, player.playerSize.width.isFinite else { return 200 }
return [player.playerSize.width, 600].min()!
} }
@ViewBuilder var controlsBackground: some View { @ViewBuilder var controlsBackground: some View {

View File

@ -0,0 +1,25 @@
import Defaults
import SwiftUI
struct VideoDetailsOverlay: View {
@EnvironmentObject<PlayerControlsModel> private var controls
var body: some View {
VideoDetails(sidebarQueue: false, fullScreen: fullScreenBinding)
}
var fullScreenBinding: Binding<Bool> {
.init(get: {
controls.presentingDetailsOverlay
}, set: { newValue in
controls.presentingDetailsOverlay = newValue
})
}
}
struct VideoDetailsOverlay_Previews: PreviewProvider {
static var previews: some View {
VideoDetailsOverlay()
.injectFixtureEnvironmentObjects()
}
}

View File

@ -9,17 +9,7 @@ struct PlayerGestures: View {
gestureRectangle gestureRectangle
.tapRecognizer( .tapRecognizer(
tapSensitivity: 0.2, tapSensitivity: 0.2,
singleTapAction: { singleTapAction: { singleTapAction() },
if model.presentingControlsOverlay {
model.presentingControls = true
model.resetTimer()
withAnimation(PlayerControls.animation) {
model.presentingControlsOverlay = false
}
} else {
model.toggle()
}
},
doubleTapAction: { doubleTapAction: {
player.backend.seek(relative: .secondsInDefaultTimescale(-10)) player.backend.seek(relative: .secondsInDefaultTimescale(-10))
}, },
@ -31,17 +21,7 @@ struct PlayerGestures: View {
gestureRectangle gestureRectangle
.tapRecognizer( .tapRecognizer(
tapSensitivity: 0.2, tapSensitivity: 0.2,
singleTapAction: { singleTapAction: { singleTapAction() },
if model.presentingControlsOverlay {
model.presentingControls = true
model.resetTimer()
withAnimation(PlayerControls.animation) {
model.presentingControlsOverlay = false
}
} else {
model.toggle()
}
},
doubleTapAction: { doubleTapAction: {
player.backend.togglePlay() player.backend.togglePlay()
}, },
@ -53,17 +33,7 @@ struct PlayerGestures: View {
gestureRectangle gestureRectangle
.tapRecognizer( .tapRecognizer(
tapSensitivity: 0.2, tapSensitivity: 0.2,
singleTapAction: { singleTapAction: { singleTapAction() },
if model.presentingControlsOverlay {
model.presentingControls = true
model.resetTimer()
withAnimation(PlayerControls.animation) {
model.presentingControlsOverlay = false
}
} else {
model.toggle()
}
},
doubleTapAction: { doubleTapAction: {
player.backend.seek(relative: .secondsInDefaultTimescale(10)) player.backend.seek(relative: .secondsInDefaultTimescale(10))
}, },
@ -74,6 +44,16 @@ struct PlayerGestures: View {
} }
} }
func singleTapAction() {
if model.presentingOverlays {
withAnimation(PlayerControls.animation) {
model.hideOverlays()
}
} else {
model.toggle()
}
}
var gestureRectangle: some View { var gestureRectangle: some View {
Color.clear Color.clear
.contentShape(Rectangle()) .contentShape(Rectangle())

View File

@ -26,34 +26,32 @@ struct PlayerQueueRow: View {
} }
var body: some View { var body: some View {
Group { Button {
Button { player.prepareCurrentItemForHistory()
player.prepareCurrentItemForHistory()
player.avPlayerBackend.startPictureInPictureOnPlay = player.playingInPictureInPicture player.avPlayerBackend.startPictureInPictureOnPlay = player.playingInPictureInPicture
player.videoBeingOpened = item.video player.videoBeingOpened = item.video
if history { if history {
player.playHistory(item, at: watchStoppedAt) player.playHistory(item, at: watchStoppedAt)
} else { } else {
player.advanceToItem(item, at: watchStoppedAt) player.advanceToItem(item, at: watchStoppedAt)
}
if fullScreen {
withAnimation {
fullScreen = false
}
}
if closePiPOnNavigation, player.playingInPictureInPicture {
player.closePiP()
}
} label: {
VideoBanner(video: item.video, playbackTime: watchStoppedAt, videoDuration: watch?.videoDuration)
} }
.buttonStyle(.plain)
if fullScreen {
withAnimation {
fullScreen = false
}
}
if closePiPOnNavigation, player.playingInPictureInPicture {
player.closePiP()
}
} label: {
VideoBanner(video: item.video, playbackTime: watchStoppedAt, videoDuration: watch?.videoDuration)
} }
.buttonStyle(.plain)
} }
private var watch: Watch? { private var watch: Watch? {

View File

@ -28,9 +28,10 @@ struct PlayerQueueView: View {
playedPreviously playedPreviously
} }
} }
.listRowBackground(Color.clear)
#if !os(iOS) #if !os(iOS)
.padding(.vertical, 5) .padding(.vertical, 5)
.listRowInsets(EdgeInsets()) .listRowInsets(EdgeInsets())
#endif #endif
} }
@ -38,6 +39,8 @@ struct PlayerQueueView: View {
.listStyle(.inset) .listStyle(.inset)
#elseif os(iOS) #elseif os(iOS)
.listStyle(.grouped) .listStyle(.grouped)
.backport
.scrollContentBackground(false)
#else #else
.listStyle(.plain) .listStyle(.plain)
#endif #endif

View File

@ -13,6 +13,7 @@ struct RelatedView: View {
Section(header: Text("Related")) { Section(header: Text("Related")) {
ForEach(related) { video in ForEach(related) { video in
PlayerQueueRow(item: PlayerQueueItem(video)) PlayerQueueRow(item: PlayerQueueItem(video))
.listRowBackground(Color.clear)
.contextMenu { .contextMenu {
Section { Section {
Button { Button {
@ -53,6 +54,8 @@ struct RelatedView: View {
.listStyle(.inset) .listStyle(.inset)
#elseif os(iOS) #elseif os(iOS)
.listStyle(.grouped) .listStyle(.grouped)
.backport
.scrollContentBackground(false)
#else #else
.listStyle(.plain) .listStyle(.plain)
#endif #endif

View File

@ -108,7 +108,6 @@ struct VideoDetails: View {
page.update(.moveToLast) page.update(.moveToLast)
} }
} }
.edgesIgnoringSafeArea(.horizontal)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading) .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
} }
@ -178,18 +177,14 @@ struct VideoDetails: View {
} }
case .chapters: case .chapters:
ChaptersView() ChaptersView()
.edgesIgnoringSafeArea(.horizontal)
case .queue: case .queue:
PlayerQueueView(sidebarQueue: sidebarQueue, fullScreen: $fullScreen) PlayerQueueView(sidebarQueue: sidebarQueue, fullScreen: $fullScreen)
.edgesIgnoringSafeArea(.horizontal)
case .related: case .related:
RelatedView() RelatedView()
.edgesIgnoringSafeArea(.horizontal)
case .comments: case .comments:
CommentsView(embedInScrollView: true) CommentsView(embedInScrollView: true)
.edgesIgnoringSafeArea(.horizontal)
} }
} }
.contentShape(Rectangle()) .contentShape(Rectangle())
@ -209,16 +204,16 @@ struct VideoDetails: View {
VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 10) {
if !player.videoBeingOpened.isNil && (video.description.isNil || video.description!.isEmpty) { if !player.videoBeingOpened.isNil && (video.description.isNil || video.description!.isEmpty) {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
ForEach(1 ... Int.random(in: 3 ... 5), id: \.self) { _ in Text(String(repeating: Video.fixture.description ?? "", count: Int.random(in: 1 ... 30)))
Text(String(repeating: Video.fixture.description!, count: Int.random(in: 1 ... 4))) .redacted(reason: .placeholder)
.redacted(reason: .placeholder)
}
} }
} else if let description = video.description { } else if let description = video.description {
Group { Group {
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) { if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
Text(description) Text(description)
#if !os(tvOS)
.textSelection(.enabled) .textSelection(.enabled)
#endif
} else { } else {
Text(description) Text(description)
} }

View File

@ -119,6 +119,7 @@ struct VideoPlayerView: View {
} }
viewVerticalOffset = Self.hiddenOffset viewVerticalOffset = Self.hiddenOffset
stopOrientationUpdates() stopOrientationUpdates()
player.controls.hideOverlays()
} }
} }
#endif #endif
@ -203,9 +204,9 @@ struct VideoPlayerView: View {
hoveringPlayer = hovering hoveringPlayer = hovering
hovering ? playerControls.show() : playerControls.hide() hovering ? playerControls.show() : playerControls.hide()
} }
#if !os(macOS) #if os(iOS)
.gesture(playerDragGesture) .gesture(isPlayerDragGestureEnabled ? playerDragGesture : nil)
#else #elseif os(macOS)
.onAppear(perform: { .onAppear(perform: {
NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) { NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) {
if !player.currentItem.isNil, hoveringPlayer { if !player.currentItem.isNil, hoveringPlayer {
@ -296,6 +297,9 @@ struct VideoPlayerView: View {
.onChange(of: proxy.size) { _ in .onChange(of: proxy.size) { _ in
player.playerSize = proxy.size player.playerSize = proxy.size
} }
.onChange(of: player.controls.presentingOverlays) { _ in
player.playerSize = proxy.size
}
}) })
#if os(iOS) #if os(iOS)
.padding(.top, player.playingFullScreen && verticalSizeClass == .regular ? 20 : 0) .padding(.top, player.playingFullScreen && verticalSizeClass == .regular ? 20 : 0)
@ -351,6 +355,10 @@ struct VideoPlayerView: View {
} }
} }
var isPlayerDragGestureEnabled: Bool {
!player.controls.presentingDetailsOverlay && !player.controls.presentingDetailsOverlay
}
var controlsTopPadding: Double { var controlsTopPadding: Double {
guard fullScreenLayout else { return 0 } guard fullScreenLayout else { return 0 }

View File

@ -24,6 +24,7 @@ struct ControlsBar: View {
var borderBottom = true var borderBottom = true
var detailsTogglePlayer = true var detailsTogglePlayer = true
var detailsToggleFullScreen = false var detailsToggleFullScreen = false
var titleLineLimit = 2
var body: some View { var body: some View {
HStack(spacing: 0) { HStack(spacing: 0) {
@ -69,7 +70,9 @@ struct ControlsBar: View {
details details
.contentShape(Rectangle()) .contentShape(Rectangle())
} }
#if !os(tvOS)
.keyboardShortcut("t") .keyboardShortcut("t")
#endif
} else { } else {
details details
} }
@ -139,7 +142,9 @@ struct ControlsBar: View {
subscriptions.isSubscribing(video.channel.id) subscriptions.isSubscribing(video.channel.id)
{ {
Image(systemName: "star.circle.fill") Image(systemName: "star.circle.fill")
#if !os(tvOS)
.background(Color.background) .background(Color.background)
#endif
.clipShape(Circle()) .clipShape(Circle())
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
@ -166,7 +171,9 @@ struct ControlsBar: View {
} }
} }
ShareButton(contentItem: .init(video: model.currentVideo)) #if !os(tvOS)
ShareButton(contentItem: .init(video: model.currentVideo))
#endif
Section { Section {
Button { Button {
@ -215,12 +222,14 @@ struct ControlsBar: View {
} }
} }
VStack(alignment: .leading, spacing: 5) { VStack(alignment: .leading, spacing: 0) {
Text(model.currentVideo?.title ?? "Not playing") Text(model.currentVideo?.title ?? "Not playing")
.font(.system(size: 14)) .font(.system(size: 14))
.fontWeight(.semibold) .fontWeight(.semibold)
.foregroundColor(model.currentVideo.isNil ? .secondary : .accentColor) .foregroundColor(model.currentVideo.isNil ? .secondary : .accentColor)
.lineLimit(1) .fixedSize(horizontal: false, vertical: true)
.lineLimit(titleLineLimit)
.multilineTextAlignment(.leading)
if let video = model.currentVideo { if let video = model.currentVideo {
HStack(spacing: 2) { HStack(spacing: 2) {

View File

@ -714,6 +714,14 @@
37E70927271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E70926271CDDAE00D34DDE /* OpenSettingsButton.swift */; }; 37E70927271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E70926271CDDAE00D34DDE /* OpenSettingsButton.swift */; };
37E70928271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E70926271CDDAE00D34DDE /* OpenSettingsButton.swift */; }; 37E70928271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E70926271CDDAE00D34DDE /* OpenSettingsButton.swift */; };
37E70929271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E70926271CDDAE00D34DDE /* OpenSettingsButton.swift */; }; 37E70929271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E70926271CDDAE00D34DDE /* OpenSettingsButton.swift */; };
37E80F3C287B107F00561799 /* VideoDetailsOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E80F3B287B107F00561799 /* VideoDetailsOverlay.swift */; };
37E80F3D287B107F00561799 /* VideoDetailsOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E80F3B287B107F00561799 /* VideoDetailsOverlay.swift */; };
37E80F40287B472300561799 /* ScrollContentBackground+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E80F3F287B472300561799 /* ScrollContentBackground+Backport.swift */; };
37E80F43287B7AAF00561799 /* SwiftUIPager in Frameworks */ = {isa = PBXBuildFile; productRef = 37E80F42287B7AAF00561799 /* SwiftUIPager */; };
37E80F44287B7AB400561799 /* VideoDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B81AFE26D2CA3700675966 /* VideoDetails.swift */; };
37E80F45287B7AC000561799 /* ControlsBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372CFD14285F2E2A00B0B54B /* ControlsBar.swift */; };
37E80F46287B7AEC00561799 /* PlayerQueueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC3F4B270CFE1700608308 /* PlayerQueueView.swift */; };
37E80F47287B7B9400561799 /* VideoDetailsOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E80F3B287B107F00561799 /* VideoDetailsOverlay.swift */; };
37E8B0EC27B326C00024006F /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E8B0EB27B326C00024006F /* TimelineView.swift */; }; 37E8B0EC27B326C00024006F /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E8B0EB27B326C00024006F /* TimelineView.swift */; };
37E8B0ED27B326C00024006F /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E8B0EB27B326C00024006F /* TimelineView.swift */; }; 37E8B0ED27B326C00024006F /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E8B0EB27B326C00024006F /* TimelineView.swift */; };
37E8B0EE27B326C00024006F /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E8B0EB27B326C00024006F /* TimelineView.swift */; }; 37E8B0EE27B326C00024006F /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E8B0EB27B326C00024006F /* TimelineView.swift */; };
@ -1179,6 +1187,8 @@
37E64DD026D597EB00C71877 /* SubscriptionsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionsModel.swift; sourceTree = "<group>"; }; 37E64DD026D597EB00C71877 /* SubscriptionsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionsModel.swift; sourceTree = "<group>"; };
37E70922271CD43000D34DDE /* WelcomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreen.swift; sourceTree = "<group>"; }; 37E70922271CD43000D34DDE /* WelcomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreen.swift; sourceTree = "<group>"; };
37E70926271CDDAE00D34DDE /* OpenSettingsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSettingsButton.swift; sourceTree = "<group>"; }; 37E70926271CDDAE00D34DDE /* OpenSettingsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSettingsButton.swift; sourceTree = "<group>"; };
37E80F3B287B107F00561799 /* VideoDetailsOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDetailsOverlay.swift; sourceTree = "<group>"; };
37E80F3F287B472300561799 /* ScrollContentBackground+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ScrollContentBackground+Backport.swift"; sourceTree = "<group>"; };
37E8B0EB27B326C00024006F /* TimelineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; }; 37E8B0EB27B326C00024006F /* TimelineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; };
37E8B0EF27B326F30024006F /* Comparable+Clamped.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Comparable+Clamped.swift"; sourceTree = "<group>"; }; 37E8B0EF27B326F30024006F /* Comparable+Clamped.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Comparable+Clamped.swift"; sourceTree = "<group>"; };
37EAD86A267B9C5600D9E01B /* SponsorBlockAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SponsorBlockAPI.swift; sourceTree = "<group>"; }; 37EAD86A267B9C5600D9E01B /* SponsorBlockAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SponsorBlockAPI.swift; sourceTree = "<group>"; };
@ -1369,6 +1379,7 @@
3772003A27E8EEBE00CB2475 /* CoreMedia.framework in Frameworks */, 3772003A27E8EEBE00CB2475 /* CoreMedia.framework in Frameworks */,
372915E42687E33E00F5A35B /* Defaults in Frameworks */, 372915E42687E33E00F5A35B /* Defaults in Frameworks */,
3772003B27E8EEC800CB2475 /* libbz2.tbd in Frameworks */, 3772003B27E8EEC800CB2475 /* libbz2.tbd in Frameworks */,
37E80F43287B7AAF00561799 /* SwiftUIPager in Frameworks */,
37BADCA9269A570B009BE4FB /* Alamofire in Frameworks */, 37BADCA9269A570B009BE4FB /* Alamofire in Frameworks */,
37D4B19D2671817900C925CA /* SwiftyJSON in Frameworks */, 37D4B19D2671817900C925CA /* SwiftyJSON in Frameworks */,
3797757D268922D100DD52A8 /* Siesta in Frameworks */, 3797757D268922D100DD52A8 /* Siesta in Frameworks */,
@ -1437,6 +1448,7 @@
37F13B61285E43C000B137E4 /* ControlsOverlay.swift */, 37F13B61285E43C000B137E4 /* ControlsOverlay.swift */,
37030FFE27B04DCC00ECDDAA /* PlayerControls.swift */, 37030FFE27B04DCC00ECDDAA /* PlayerControls.swift */,
37648B68286CF5F1003D330B /* TVControls.swift */, 37648B68286CF5F1003D330B /* TVControls.swift */,
37E80F3B287B107F00561799 /* VideoDetailsOverlay.swift */,
); );
path = Controls; path = Controls;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1554,10 +1566,11 @@
children = ( children = (
3722AEBD274DA401005EA4D6 /* Backport.swift */, 3722AEBD274DA401005EA4D6 /* Backport.swift */,
3722AEBB274DA396005EA4D6 /* Badge+Backport.swift */, 3722AEBB274DA396005EA4D6 /* Badge+Backport.swift */,
3722AEBF274DAEB8005EA4D6 /* Tint+Backport.swift */,
3727B74727872A500021C15E /* VisualEffectBlur-macOS.swift */,
3727B74927872A920021C15E /* VisualEffectBlur-iOS.swift */,
37136CAB286273060095C0CF /* PersistentSystemOverlays+Backport.swift */, 37136CAB286273060095C0CF /* PersistentSystemOverlays+Backport.swift */,
37E80F3F287B472300561799 /* ScrollContentBackground+Backport.swift */,
3722AEBF274DAEB8005EA4D6 /* Tint+Backport.swift */,
3727B74927872A920021C15E /* VisualEffectBlur-iOS.swift */,
3727B74727872A500021C15E /* VisualEffectBlur-macOS.swift */,
); );
path = Backports; path = Backports;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2311,6 +2324,7 @@
3765917D27237D2A009F956E /* PINCache */, 3765917D27237D2A009F956E /* PINCache */,
37CF8B8728535E6300B71E37 /* SDWebImage */, 37CF8B8728535E6300B71E37 /* SDWebImage */,
372AA411286D06950000B1DC /* Repeat */, 372AA411286D06950000B1DC /* Repeat */,
37E80F42287B7AAF00561799 /* SwiftUIPager */,
); );
productName = Yattee; productName = Yattee;
productReference = 37D4B158267164AE00C925CA /* Yattee.app */; productReference = 37D4B158267164AE00C925CA /* Yattee.app */;
@ -2674,6 +2688,7 @@
378E50FF26FE8EEE00F49626 /* AccountsMenuView.swift in Sources */, 378E50FF26FE8EEE00F49626 /* AccountsMenuView.swift in Sources */,
37169AA62729E2CC0011DE61 /* AccountsBridge.swift in Sources */, 37169AA62729E2CC0011DE61 /* AccountsBridge.swift in Sources */,
37C7A1DA267CACF50010EAD6 /* TrendingCountry.swift in Sources */, 37C7A1DA267CACF50010EAD6 /* TrendingCountry.swift in Sources */,
37E80F40287B472300561799 /* ScrollContentBackground+Backport.swift in Sources */,
3727B74A27872A920021C15E /* VisualEffectBlur-iOS.swift in Sources */, 3727B74A27872A920021C15E /* VisualEffectBlur-iOS.swift in Sources */,
37977583268922F600DD52A8 /* InvidiousAPI.swift in Sources */, 37977583268922F600DD52A8 /* InvidiousAPI.swift in Sources */,
37130A5F277657300033018A /* PersistenceController.swift in Sources */, 37130A5F277657300033018A /* PersistenceController.swift in Sources */,
@ -2836,6 +2851,7 @@
37BD07BB2698AB60003EBB87 /* AppSidebarNavigation.swift in Sources */, 37BD07BB2698AB60003EBB87 /* AppSidebarNavigation.swift in Sources */,
37C0697A2725C09E00F7F6CB /* PlayerQueueItemBridge.swift in Sources */, 37C0697A2725C09E00F7F6CB /* PlayerQueueItemBridge.swift in Sources */,
379B0253287A1CDF001015B5 /* OrientationTracker.swift in Sources */, 379B0253287A1CDF001015B5 /* OrientationTracker.swift in Sources */,
37E80F3C287B107F00561799 /* VideoDetailsOverlay.swift in Sources */,
370B79C9286279810045DB77 /* NSObject+Swizzle.swift in Sources */, 370B79C9286279810045DB77 /* NSObject+Swizzle.swift in Sources */,
37D4B0E42671614900C925CA /* YatteeApp.swift in Sources */, 37D4B0E42671614900C925CA /* YatteeApp.swift in Sources */,
37C3A241272359900087A57A /* Double+Format.swift in Sources */, 37C3A241272359900087A57A /* Double+Format.swift in Sources */,
@ -2886,6 +2902,7 @@
374C053C2724614F009BDDBE /* PlayerTVMenu.swift in Sources */, 374C053C2724614F009BDDBE /* PlayerTVMenu.swift in Sources */,
377FC7DD267A081A00A6BBAF /* PopularView.swift in Sources */, 377FC7DD267A081A00A6BBAF /* PopularView.swift in Sources */,
3784CDE327772EE40055BBF2 /* Watch.swift in Sources */, 3784CDE327772EE40055BBF2 /* Watch.swift in Sources */,
37E80F3D287B107F00561799 /* VideoDetailsOverlay.swift in Sources */,
37DD9DBB2785D60300539416 /* FramePreferenceKey.swift in Sources */, 37DD9DBB2785D60300539416 /* FramePreferenceKey.swift in Sources */,
375DFB5926F9DA010013F468 /* InstancesModel.swift in Sources */, 375DFB5926F9DA010013F468 /* InstancesModel.swift in Sources */,
3705B183267B4E4900704544 /* TrendingCategory.swift in Sources */, 3705B183267B4E4900704544 /* TrendingCategory.swift in Sources */,
@ -3159,6 +3176,7 @@
37732FF22703A26300F04329 /* AccountValidationStatus.swift in Sources */, 37732FF22703A26300F04329 /* AccountValidationStatus.swift in Sources */,
3756C2AC2861151C00E4B059 /* NetworkStateModel.swift in Sources */, 3756C2AC2861151C00E4B059 /* NetworkStateModel.swift in Sources */,
37A5DBCA285E371400CA4DD1 /* ControlBackgroundModifier.swift in Sources */, 37A5DBCA285E371400CA4DD1 /* ControlBackgroundModifier.swift in Sources */,
37E80F46287B7AEC00561799 /* PlayerQueueView.swift in Sources */,
37C0698427260B2100F7F6CB /* ThumbnailsModel.swift in Sources */, 37C0698427260B2100F7F6CB /* ThumbnailsModel.swift in Sources */,
37666BAA27023AF000F869E5 /* AccountSelectionView.swift in Sources */, 37666BAA27023AF000F869E5 /* AccountSelectionView.swift in Sources */,
3765788B2685471400D4EA09 /* Playlist.swift in Sources */, 3765788B2685471400D4EA09 /* Playlist.swift in Sources */,
@ -3168,6 +3186,8 @@
3752069B285E8DD300CA655F /* Chapter.swift in Sources */, 3752069B285E8DD300CA655F /* Chapter.swift in Sources */,
37B044B926F7AB9000E1419D /* SettingsView.swift in Sources */, 37B044B926F7AB9000E1419D /* SettingsView.swift in Sources */,
3743B86A27216D3600261544 /* ChannelCell.swift in Sources */, 3743B86A27216D3600261544 /* ChannelCell.swift in Sources */,
37E80F47287B7B9400561799 /* VideoDetailsOverlay.swift in Sources */,
37E80F44287B7AB400561799 /* VideoDetails.swift in Sources */,
3751BA8527E6914F007B1A60 /* ReturnYouTubeDislikeAPI.swift in Sources */, 3751BA8527E6914F007B1A60 /* ReturnYouTubeDislikeAPI.swift in Sources */,
37030FFD27B0398000ECDDAA /* MPVClient.swift in Sources */, 37030FFD27B0398000ECDDAA /* MPVClient.swift in Sources */,
37B767DD2677C3CA0098BAA8 /* PlayerModel.swift in Sources */, 37B767DD2677C3CA0098BAA8 /* PlayerModel.swift in Sources */,
@ -3180,6 +3200,7 @@
371B7E682759786B00D21217 /* Comment+Fixtures.swift in Sources */, 371B7E682759786B00D21217 /* Comment+Fixtures.swift in Sources */,
37BE0BD126A0E2D50092E2DB /* VideoPlayerView.swift in Sources */, 37BE0BD126A0E2D50092E2DB /* VideoPlayerView.swift in Sources */,
37AAF27E26737323007FC770 /* PopularView.swift in Sources */, 37AAF27E26737323007FC770 /* PopularView.swift in Sources */,
37E80F45287B7AC000561799 /* ControlsBar.swift in Sources */,
3743CA50270EFE3400E4D32B /* PlayerQueueRow.swift in Sources */, 3743CA50270EFE3400E4D32B /* PlayerQueueRow.swift in Sources */,
376BE50827347B57009AD608 /* SettingsHeader.swift in Sources */, 376BE50827347B57009AD608 /* SettingsHeader.swift in Sources */,
37A9966026D6F9B9006E3224 /* FavoritesView.swift in Sources */, 37A9966026D6F9B9006E3224 /* FavoritesView.swift in Sources */,
@ -3900,7 +3921,7 @@
37D4B178267164B000C925CA /* Debug */ = { 37D4B178267164B000C925CA /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 71; CURRENT_PROJECT_VERSION = 71;
@ -3909,7 +3930,6 @@
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = tvOS/Info.plist; INFOPLIST_FILE = tvOS/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Yattee;
INFOPLIST_KEY_CFBundleName = "Yattee (Apple TV)"; INFOPLIST_KEY_CFBundleName = "Yattee (Apple TV)";
INFOPLIST_KEY_CFBundleVersion = 1; INFOPLIST_KEY_CFBundleVersion = 1;
INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@ -3938,7 +3958,7 @@
37D4B179267164B000C925CA /* Release */ = { 37D4B179267164B000C925CA /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 71; CURRENT_PROJECT_VERSION = 71;
@ -3947,7 +3967,6 @@
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = tvOS/Info.plist; INFOPLIST_FILE = tvOS/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Yattee;
INFOPLIST_KEY_CFBundleName = "Yattee (Apple TV)"; INFOPLIST_KEY_CFBundleName = "Yattee (Apple TV)";
INFOPLIST_KEY_CFBundleVersion = 1; INFOPLIST_KEY_CFBundleVersion = 1;
INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@ -4470,6 +4489,11 @@
package = 37D4B19B2671817900C925CA /* XCRemoteSwiftPackageReference "SwiftyJSON" */; package = 37D4B19B2671817900C925CA /* XCRemoteSwiftPackageReference "SwiftyJSON" */;
productName = SwiftyJSON; productName = SwiftyJSON;
}; };
37E80F42287B7AAF00561799 /* SwiftUIPager */ = {
isa = XCSwiftPackageProductDependency;
package = 37A5DBC2285DFF5400CA4DD1 /* XCRemoteSwiftPackageReference "SwiftUIPager" */;
productName = SwiftUIPager;
};
37FB28452722054C00A57617 /* SDWebImageSwiftUI */ = { 37FB28452722054C00A57617 /* SDWebImageSwiftUI */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = 37FB28442722054B00A57617 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */; package = 37FB28442722054B00A57617 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */;