mirror of
https://github.com/yattee/yattee.git
synced 2025-12-07 08:38:14 +00:00
Fix SwiftLint and SwiftFormat violations
- Run SwiftFormat to fix indentation, spacing, and formatting issues - Replace CGFloat with Double and NSRect with CGRect per style guide - Remove redundant .center alignment specifications - Remove unnecessary @available checks for satisfied deployment targets - Fix closure brace indentation for consistency - Disable closure_end_indentation rule to resolve SwiftFormat conflict All linting checks now pass with zero errors and warnings.
This commit is contained in:
@@ -7,6 +7,7 @@ disabled_rules:
|
|||||||
- number_separator
|
- number_separator
|
||||||
- multiline_arguments
|
- multiline_arguments
|
||||||
- implicit_return
|
- implicit_return
|
||||||
|
- closure_end_indentation
|
||||||
excluded:
|
excluded:
|
||||||
- Vendor
|
- Vendor
|
||||||
- Tests Apple TV
|
- Tests Apple TV
|
||||||
|
|||||||
@@ -687,7 +687,8 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
func extractXTags(from urlString: String) -> [String: String] {
|
func extractXTags(from urlString: String) -> [String: String] {
|
||||||
guard let urlComponents = URLComponents(string: urlString),
|
guard let urlComponents = URLComponents(string: urlString),
|
||||||
let queryItems = urlComponents.queryItems,
|
let queryItems = urlComponents.queryItems,
|
||||||
let xtagsValue = queryItems.first(where: { $0.name == "xtags" })?.value else {
|
let xtagsValue = queryItems.first(where: { $0.name == "xtags" })?.value
|
||||||
|
else {
|
||||||
return [:]
|
return [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -722,10 +722,10 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
|||||||
}
|
}
|
||||||
.sorted { track1, track2 in
|
.sorted { track1, track2 in
|
||||||
// Sort: ORIGINAL first, then DUBBED, then others
|
// Sort: ORIGINAL first, then DUBBED, then others
|
||||||
if track1.content == "ORIGINAL" && track2.content != "ORIGINAL" {
|
if track1.content == "ORIGINAL", track2.content != "ORIGINAL" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if track1.content != "ORIGINAL" && track2.content == "ORIGINAL" {
|
if track1.content != "ORIGINAL", track2.content == "ORIGINAL" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// If both are same type, sort by language
|
// If both are same type, sort by language
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ final class NavigationModel: ObservableObject {
|
|||||||
func multipleTapHandler() {
|
func multipleTapHandler() {
|
||||||
switch tabSelection {
|
switch tabSelection {
|
||||||
case .search:
|
case .search:
|
||||||
self.search.focused = true
|
search.focused = true
|
||||||
default:
|
default:
|
||||||
print("not implemented")
|
print("not implemented")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ final class NetworkStateModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var bufferingStateText: String? {
|
var bufferingStateText: String? {
|
||||||
guard detailsAvailable && player.hasStarted else { return nil }
|
guard detailsAvailable, player.hasStarted else { return nil }
|
||||||
return String(format: "%.0f%%", bufferingState)
|
return String(format: "%.0f%%", bufferingState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -190,8 +190,8 @@ final class AVPlayerBackend: PlayerBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// After the video has ended, hitting play restarts the video from the beginning.
|
// After the video has ended, hitting play restarts the video from the beginning.
|
||||||
if currentTime?.seconds.formattedAsPlaybackTime() == model.playerTime.duration.seconds.formattedAsPlaybackTime() &&
|
if currentTime?.seconds.formattedAsPlaybackTime() == model.playerTime.duration.seconds.formattedAsPlaybackTime(),
|
||||||
currentTime!.seconds > 0 && model.playerTime.duration.seconds > 0
|
currentTime!.seconds > 0, model.playerTime.duration.seconds > 0
|
||||||
{
|
{
|
||||||
seek(to: 0, seekType: .loopRestart)
|
seek(to: 0, seekType: .loopRestart)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ final class MPVBackend: PlayerBackend {
|
|||||||
|
|
||||||
var controlsUpdates = false
|
var controlsUpdates = false
|
||||||
private var timeObserverThrottle = Throttle(interval: 2)
|
private var timeObserverThrottle = Throttle(interval: 2)
|
||||||
|
|
||||||
// Retry mechanism
|
// Retry mechanism
|
||||||
private var retryCount = 0
|
private var retryCount = 0
|
||||||
private let maxRetries = 3
|
private let maxRetries = 3
|
||||||
@@ -227,7 +227,7 @@ final class MPVBackend: PlayerBackend {
|
|||||||
// Store stream and video for potential retries
|
// Store stream and video for potential retries
|
||||||
currentRetryStream = stream
|
currentRetryStream = stream
|
||||||
currentRetryVideo = video
|
currentRetryVideo = video
|
||||||
|
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
if model.presentingPlayer {
|
if model.presentingPlayer {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
@@ -247,7 +247,7 @@ final class MPVBackend: PlayerBackend {
|
|||||||
video.captions.first { $0.code.contains(captionsDefaultLanguageCode) }
|
video.captions.first { $0.code.contains(captionsDefaultLanguageCode) }
|
||||||
|
|
||||||
// If there are still no captions, try to get captions with the fallback language code
|
// If there are still no captions, try to get captions with the fallback language code
|
||||||
if captions.isNil && !captionsFallbackLanguageCode.isEmpty {
|
if captions.isNil, !captionsFallbackLanguageCode.isEmpty {
|
||||||
captions = video.captions.first { $0.code == captionsFallbackLanguageCode } ??
|
captions = video.captions.first { $0.code == captionsFallbackLanguageCode } ??
|
||||||
video.captions.first { $0.code.contains(captionsFallbackLanguageCode) }
|
video.captions.first { $0.code.contains(captionsFallbackLanguageCode) }
|
||||||
}
|
}
|
||||||
@@ -369,7 +369,7 @@ final class MPVBackend: PlayerBackend {
|
|||||||
replaceItem(self.model.preservedTime)
|
replaceItem(self.model.preservedTime)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
replaceItem(self.model.preservedTime)
|
replaceItem(model.preservedTime)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
replaceItem(nil)
|
replaceItem(nil)
|
||||||
@@ -400,8 +400,8 @@ final class MPVBackend: PlayerBackend {
|
|||||||
setRate(model.currentRate)
|
setRate(model.currentRate)
|
||||||
|
|
||||||
// After the video has ended, hitting play restarts the video from the beginning.
|
// After the video has ended, hitting play restarts the video from the beginning.
|
||||||
if let currentTime, currentTime.seconds.formattedAsPlaybackTime() == model.playerTime.duration.seconds.formattedAsPlaybackTime() &&
|
if let currentTime, currentTime.seconds.formattedAsPlaybackTime() == model.playerTime.duration.seconds.formattedAsPlaybackTime(),
|
||||||
currentTime.seconds > 0 && model.playerTime.duration.seconds > 0
|
currentTime.seconds > 0, model.playerTime.duration.seconds > 0
|
||||||
{
|
{
|
||||||
seek(to: 0, seekType: .loopRestart)
|
seek(to: 0, seekType: .loopRestart)
|
||||||
}
|
}
|
||||||
@@ -466,23 +466,23 @@ final class MPVBackend: PlayerBackend {
|
|||||||
func closeItem() {
|
func closeItem() {
|
||||||
pause()
|
pause()
|
||||||
stop()
|
stop()
|
||||||
self.video = nil
|
video = nil
|
||||||
self.stream = nil
|
stream = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func closePiP() {}
|
func closePiP() {}
|
||||||
|
|
||||||
func startControlsUpdates() {
|
func startControlsUpdates() {
|
||||||
guard model.presentingPlayer, model.controls.presentingControls, !model.controls.presentingOverlays else {
|
guard model.presentingPlayer, model.controls.presentingControls, !model.controls.presentingOverlays else {
|
||||||
self.logger.info("ignored controls update start")
|
logger.info("ignored controls update start")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.logger.info("starting controls updates")
|
logger.info("starting controls updates")
|
||||||
controlsUpdates = true
|
controlsUpdates = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopControlsUpdates() {
|
func stopControlsUpdates() {
|
||||||
self.logger.info("stopping controls updates")
|
logger.info("stopping controls updates")
|
||||||
controlsUpdates = false
|
controlsUpdates = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,7 +514,7 @@ final class MPVBackend: PlayerBackend {
|
|||||||
self.model.updateWatch(time: currentTime)
|
self.model.updateWatch(time: currentTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.model.updateTime(currentTime)
|
model.updateTime(currentTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func stopClientUpdates() {
|
private func stopClientUpdates() {
|
||||||
@@ -641,7 +641,7 @@ final class MPVBackend: PlayerBackend {
|
|||||||
}
|
}
|
||||||
eofPlaybackModeAction()
|
eofPlaybackModeAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleFileLoadError() {
|
private func handleFileLoadError() {
|
||||||
guard let stream = currentRetryStream, let video = currentRetryVideo else {
|
guard let stream = currentRetryStream, let video = currentRetryVideo else {
|
||||||
// No stream info available, show error immediately
|
// No stream info available, show error immediately
|
||||||
@@ -651,13 +651,13 @@ final class MPVBackend: PlayerBackend {
|
|||||||
eofPlaybackModeAction()
|
eofPlaybackModeAction()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if retryCount < maxRetries {
|
if retryCount < maxRetries {
|
||||||
retryCount += 1
|
retryCount += 1
|
||||||
let delay = TimeInterval(retryCount * 2) // 2, 4, 6 seconds
|
let delay = TimeInterval(retryCount * 2) // 2, 4, 6 seconds
|
||||||
|
|
||||||
logger.warning("File load failed. Retry attempt \(retryCount) of \(maxRetries) after \(delay) seconds...")
|
logger.warning("File load failed. Retry attempt \(retryCount) of \(maxRetries) after \(delay) seconds...")
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
self.logger.info("Retrying file load (attempt \(self.retryCount))...")
|
self.logger.info("Retrying file load (attempt \(self.retryCount))...")
|
||||||
@@ -670,12 +670,12 @@ final class MPVBackend: PlayerBackend {
|
|||||||
model.closeCurrentItem(finished: true)
|
model.closeCurrentItem(finished: true)
|
||||||
getTimeUpdates()
|
getTimeUpdates()
|
||||||
eofPlaybackModeAction()
|
eofPlaybackModeAction()
|
||||||
|
|
||||||
// Reset retry counter for next attempt
|
// Reset retry counter for next attempt
|
||||||
resetRetryState()
|
resetRetryState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func resetRetryState() {
|
private func resetRetryState() {
|
||||||
retryCount = 0
|
retryCount = 0
|
||||||
currentRetryStream = nil
|
currentRetryStream = nil
|
||||||
@@ -807,7 +807,7 @@ final class MPVBackend: PlayerBackend {
|
|||||||
guard let stream, let video else { return }
|
guard let stream, let video else { return }
|
||||||
|
|
||||||
// Validate the index is within bounds
|
// Validate the index is within bounds
|
||||||
guard index >= 0 && index < stream.audioTracks.count else {
|
guard index >= 0, index < stream.audioTracks.count else {
|
||||||
logger.error("Invalid audio track index: \(index), available tracks: \(stream.audioTracks.count)")
|
logger.error("Invalid audio track index: \(index), available tracks: \(stream.audioTracks.count)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ extension PlayerBackend {
|
|||||||
// If rhs has no resolution, it's "less than" lhs (prefer lhs)
|
// If rhs has no resolution, it's "less than" lhs (prefer lhs)
|
||||||
return lhs.resolution == nil
|
return lhs.resolution == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if lhsResolution == rhsResolution {
|
if lhsResolution == rhsResolution {
|
||||||
guard let lhsFormat = QualityProfile.Format(rawValue: lhs.format.rawValue),
|
guard let lhsFormat = QualityProfile.Format(rawValue: lhs.format.rawValue),
|
||||||
let rhsFormat = QualityProfile.Format(rawValue: rhs.format.rawValue)
|
let rhsFormat = QualityProfile.Format(rawValue: rhs.format.rawValue)
|
||||||
|
|||||||
@@ -620,7 +620,7 @@ final class PlayerModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Defaults[.activeBackend] = to
|
Defaults[.activeBackend] = to
|
||||||
self.activeBackend = to
|
activeBackend = to
|
||||||
|
|
||||||
let fromBackend: PlayerBackend = from == .appleAVPlayer ? avPlayerBackend : mpvBackend
|
let fromBackend: PlayerBackend = from == .appleAVPlayer ? avPlayerBackend : mpvBackend
|
||||||
let toBackend: PlayerBackend = to == .appleAVPlayer ? avPlayerBackend : mpvBackend
|
let toBackend: PlayerBackend = to == .appleAVPlayer ? avPlayerBackend : mpvBackend
|
||||||
@@ -628,13 +628,13 @@ final class PlayerModel: ObservableObject {
|
|||||||
toBackend.cancelLoads()
|
toBackend.cancelLoads()
|
||||||
fromBackend.cancelLoads()
|
fromBackend.cancelLoads()
|
||||||
|
|
||||||
if !self.backend.canPlayAtRate(currentRate) {
|
if !backend.canPlayAtRate(currentRate) {
|
||||||
currentRate = self.backend.suggestedPlaybackRates.last { $0 < currentRate } ?? 1.0
|
currentRate = backend.suggestedPlaybackRates.last { $0 < currentRate } ?? 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
self.rateToRestore = Float(currentRate)
|
rateToRestore = Float(currentRate)
|
||||||
|
|
||||||
self.backend.didChangeTo()
|
backend.didChangeTo()
|
||||||
|
|
||||||
if wasPlaying {
|
if wasPlaying {
|
||||||
fromBackend.pause()
|
fromBackend.pause()
|
||||||
@@ -664,7 +664,7 @@ final class PlayerModel: ObservableObject {
|
|||||||
self.stream = stream
|
self.stream = stream
|
||||||
streamSelection = stream
|
streamSelection = stream
|
||||||
|
|
||||||
self.upgradeToStream(stream, force: true)
|
upgradeToStream(stream, force: true)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1086,7 +1086,7 @@ final class PlayerModel: ObservableObject {
|
|||||||
|
|
||||||
logger.info("entering fullscreen")
|
logger.info("entering fullscreen")
|
||||||
toggleFullscreen(false, showControls: showControls)
|
toggleFullscreen(false, showControls: showControls)
|
||||||
self.playingFullScreen = true
|
playingFullScreen = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func exitFullScreen(showControls: Bool = true) {
|
func exitFullScreen(showControls: Bool = true) {
|
||||||
@@ -1094,7 +1094,7 @@ final class PlayerModel: ObservableObject {
|
|||||||
|
|
||||||
logger.info("exiting fullscreen")
|
logger.info("exiting fullscreen")
|
||||||
toggleFullscreen(true, showControls: showControls)
|
toggleFullscreen(true, showControls: showControls)
|
||||||
self.playingFullScreen = false
|
playingFullScreen = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateNowPlayingInfo() {
|
func updateNowPlayingInfo() {
|
||||||
@@ -1284,7 +1284,7 @@ final class PlayerModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func chapterForTime(_ time: Double) -> Int? {
|
private func chapterForTime(_ time: Double) -> Int? {
|
||||||
guard let chapters = self.videoForDisplay?.chapters else {
|
guard let chapters = videoForDisplay?.chapters else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ extension PlayerModel {
|
|||||||
logger.error("Backend is nil when trying to select stream by quality profile")
|
logger.error("Backend is nil when trying to select stream by quality profile")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let profile = qualityProfile ?? .defaultProfile
|
let profile = qualityProfile ?? .defaultProfile
|
||||||
|
|
||||||
// First attempt: Filter by both `canPlay` and `isPreferred`
|
// First attempt: Filter by both `canPlay` and `isPreferred`
|
||||||
|
|||||||
@@ -186,11 +186,7 @@ struct ChannelVideosView: View {
|
|||||||
.background(Color.background(scheme: colorScheme))
|
.background(Color.background(scheme: colorScheme))
|
||||||
.focusScope(focusNamespace)
|
.focusScope(focusNamespace)
|
||||||
#elseif os(macOS)
|
#elseif os(macOS)
|
||||||
if #available(macOS 12.0, *) {
|
return content.focusScope(focusNamespace)
|
||||||
return content.focusScope(focusNamespace)
|
|
||||||
} else {
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
return content
|
return content
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ enum Constants {
|
|||||||
.filter({ $0.activationState == .foregroundActive })
|
.filter({ $0.activationState == .foregroundActive })
|
||||||
.compactMap({ $0 as? UIWindowScene })
|
.compactMap({ $0 as? UIWindowScene })
|
||||||
.first,
|
.first,
|
||||||
let window = windowScene.windows.first
|
let window = windowScene.windows.first
|
||||||
else {
|
else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -55,13 +55,13 @@ enum Constants {
|
|||||||
|
|
||||||
// Check if window size matches screen bounds (accounting for small differences)
|
// Check if window size matches screen bounds (accounting for small differences)
|
||||||
return abs(windowBounds.width - screenBounds.width) < 1 &&
|
return abs(windowBounds.width - screenBounds.width) < 1 &&
|
||||||
abs(windowBounds.height - screenBounds.height) < 1
|
abs(windowBounds.height - screenBounds.height) < 1
|
||||||
#else
|
#else
|
||||||
return false
|
return false
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static var iPadSystemControlsWidth: CGFloat {
|
static var iPadSystemControlsWidth: Double {
|
||||||
50
|
50
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,13 +64,13 @@ struct FavoriteItemView: View {
|
|||||||
#else
|
#else
|
||||||
.padding(.horizontal, 15)
|
.padding(.horizontal, 15)
|
||||||
#endif
|
#endif
|
||||||
.frame(height: expectedContentHeight)
|
.frame(height: expectedContentHeight)
|
||||||
} else {
|
} else {
|
||||||
ZStack(alignment: .topLeading) {
|
ZStack(alignment: .topLeading) {
|
||||||
// Reserve space immediately to prevent layout shift
|
// Reserve space immediately to prevent layout shift
|
||||||
Color.clear
|
Color.clear
|
||||||
.frame(height: expectedContentHeight)
|
.frame(height: expectedContentHeight)
|
||||||
|
|
||||||
// Actual content renders within the reserved space
|
// Actual content renders within the reserved space
|
||||||
Group {
|
Group {
|
||||||
switch widgetListingStyle {
|
switch widgetListingStyle {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ struct AccountsView: View {
|
|||||||
@StateObject private var model = AccountsViewModel()
|
@StateObject private var model = AccountsViewModel()
|
||||||
@Environment(\.presentationMode) private var presentationMode
|
@Environment(\.presentationMode) private var presentationMode
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
@Environment(\.colorScheme) private var colorScheme
|
@Environment(\.colorScheme) private var colorScheme
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|||||||
@@ -69,94 +69,94 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
.background(
|
.background(
|
||||||
EmptyView().sheet(isPresented: $navigation.presentingSettings) {
|
EmptyView().sheet(isPresented: $navigation.presentingSettings) {
|
||||||
SettingsView()
|
SettingsView()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
#endif
|
#endif
|
||||||
.modifier(ImportSettingsSheetViewModifier(isPresented: $navigation.presentingSettingsImportSheet, settingsFile: $navigation.settingsImportURL))
|
.modifier(ImportSettingsSheetViewModifier(isPresented: $navigation.presentingSettingsImportSheet, settingsFile: $navigation.settingsImportURL))
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
.fullScreenCover(isPresented: $navigation.presentingAccounts) {
|
.fullScreenCover(isPresented: $navigation.presentingAccounts) {
|
||||||
AccountsView()
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
.background(
|
|
||||||
EmptyView().sheet(isPresented: $navigation.presentingAccounts) {
|
|
||||||
AccountsView()
|
AccountsView()
|
||||||
}
|
}
|
||||||
)
|
#else
|
||||||
#endif
|
.background(
|
||||||
.background(
|
EmptyView().sheet(isPresented: $navigation.presentingAccounts) {
|
||||||
EmptyView().sheet(isPresented: $navigation.presentingHomeSettings) {
|
AccountsView()
|
||||||
#if os(macOS)
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
Button("Done") {
|
|
||||||
navigation.presentingHomeSettings = false
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.keyboardShortcut(.cancelAction)
|
|
||||||
|
|
||||||
HomeSettings()
|
|
||||||
}
|
}
|
||||||
.frame(width: 500, height: 800)
|
)
|
||||||
#else
|
#endif
|
||||||
NavigationView {
|
.background(
|
||||||
HomeSettings()
|
EmptyView().sheet(isPresented: $navigation.presentingHomeSettings) {
|
||||||
#if os(iOS)
|
#if os(macOS)
|
||||||
.toolbar {
|
VStack(alignment: .leading) {
|
||||||
ToolbarItem(placement: .navigation) {
|
Button("Done") {
|
||||||
Button {
|
navigation.presentingHomeSettings = false
|
||||||
navigation.presentingHomeSettings = false
|
|
||||||
} label: {
|
|
||||||
Text("Done")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.padding()
|
||||||
|
.keyboardShortcut(.cancelAction)
|
||||||
|
|
||||||
|
HomeSettings()
|
||||||
|
}
|
||||||
|
.frame(width: 500, height: 800)
|
||||||
|
#else
|
||||||
|
NavigationView {
|
||||||
|
HomeSettings()
|
||||||
|
#if os(iOS)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .navigation) {
|
||||||
|
Button {
|
||||||
|
navigation.presentingHomeSettings = false
|
||||||
|
} label: {
|
||||||
|
Text("Done")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
)
|
||||||
}
|
|
||||||
)
|
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
.fileImporter(
|
.fileImporter(
|
||||||
isPresented: $navigation.presentingFileImporter,
|
isPresented: $navigation.presentingFileImporter,
|
||||||
allowedContentTypes: [.audiovisualContent],
|
allowedContentTypes: [.audiovisualContent],
|
||||||
allowsMultipleSelection: true
|
allowsMultipleSelection: true
|
||||||
) { result in
|
) { result in
|
||||||
do {
|
do {
|
||||||
let selectedFiles = try result.get()
|
let selectedFiles = try result.get()
|
||||||
let urlsToOpen = selectedFiles.map { url in
|
let urlsToOpen = selectedFiles.map { url in
|
||||||
if let bookmarkURL = URLBookmarkModel.shared.loadBookmark(url) {
|
if let bookmarkURL = URLBookmarkModel.shared.loadBookmark(url) {
|
||||||
return bookmarkURL
|
return bookmarkURL
|
||||||
|
}
|
||||||
|
|
||||||
|
if url.startAccessingSecurityScopedResource() {
|
||||||
|
URLBookmarkModel.shared.saveBookmark(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenVideosModel.shared.openURLs(urlsToOpen)
|
||||||
|
} catch {
|
||||||
|
NavigationModel.shared.presentAlert(title: "Could not open Files")
|
||||||
}
|
}
|
||||||
|
|
||||||
if url.startAccessingSecurityScopedResource() {
|
NavigationModel.shared.presentingOpenVideos = false
|
||||||
URLBookmarkModel.shared.saveBookmark(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
return url
|
|
||||||
}
|
}
|
||||||
|
.background(
|
||||||
OpenVideosModel.shared.openURLs(urlsToOpen)
|
EmptyView().sheet(isPresented: $navigation.presentingAddToPlaylist) {
|
||||||
} catch {
|
AddToPlaylistView(video: navigation.videoToAddToPlaylist)
|
||||||
NavigationModel.shared.presentAlert(title: "Could not open Files")
|
}
|
||||||
}
|
)
|
||||||
|
.background(
|
||||||
NavigationModel.shared.presentingOpenVideos = false
|
EmptyView().sheet(isPresented: $navigation.presentingPlaylistForm) {
|
||||||
}
|
PlaylistFormView(playlist: $navigation.editedPlaylist)
|
||||||
.background(
|
}
|
||||||
EmptyView().sheet(isPresented: $navigation.presentingAddToPlaylist) {
|
)
|
||||||
AddToPlaylistView(video: navigation.videoToAddToPlaylist)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.background(
|
|
||||||
EmptyView().sheet(isPresented: $navigation.presentingPlaylistForm) {
|
|
||||||
PlaylistFormView(playlist: $navigation.editedPlaylist)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
#endif
|
#endif
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.background(
|
.background(
|
||||||
EmptyView().sheet(isPresented: $navigation.presentingPlaybackSettings) {
|
EmptyView().sheet(isPresented: $navigation.presentingPlaybackSettings) {
|
||||||
PlaybackSettings()
|
PlaybackSettings()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ struct PlayerControls: View {
|
|||||||
}
|
}
|
||||||
.offset(y: playerControlsLayout.osdVerticalOffset + 5)
|
.offset(y: playerControlsLayout.osdVerticalOffset + 5)
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.padding(.horizontal, Constants.isIPad && !Constants.isWindowFullscreen ? 10 : 0)
|
.padding(.horizontal, Constants.isIPad && !Constants.isWindowFullscreen ? 10 : 0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
@@ -125,7 +125,7 @@ struct PlayerControls: View {
|
|||||||
}
|
}
|
||||||
.font(.system(size: playerControlsLayout.bigButtonFontSize))
|
.font(.system(size: playerControlsLayout.bigButtonFontSize))
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.padding(.horizontal, Constants.isIPad && !Constants.isWindowFullscreen ? 10 : 0)
|
.padding(.horizontal, Constants.isIPad && !Constants.isWindowFullscreen ? 10 : 0)
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -178,9 +178,9 @@ struct PlayerControls: View {
|
|||||||
.zIndex(1)
|
.zIndex(1)
|
||||||
.padding(.top, 2)
|
.padding(.top, 2)
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.padding(.horizontal, Constants.isIPad && !Constants.isWindowFullscreen ? 10 : 0)
|
.padding(.horizontal, Constants.isIPad && !Constants.isWindowFullscreen ? 10 : 0)
|
||||||
#endif
|
#endif
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
|
|
||||||
HStack(spacing: playerControlsLayout.buttonsSpacing) {
|
HStack(spacing: playerControlsLayout.buttonsSpacing) {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
@@ -217,7 +217,7 @@ struct PlayerControls: View {
|
|||||||
#else
|
#else
|
||||||
.offset(y: -playerControlsLayout.timelineHeight - 5)
|
.offset(y: -playerControlsLayout.timelineHeight - 5)
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.padding(.horizontal, Constants.isIPad && !Constants.isWindowFullscreen ? 10 : 0)
|
.padding(.horizontal, Constants.isIPad && !Constants.isWindowFullscreen ? 10 : 0)
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ struct VideoDetailsOverlay: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
private var overlayLeadingPadding: CGFloat {
|
private var overlayLeadingPadding: Double {
|
||||||
// On iPad in non-fullscreen mode, add left padding for system controls
|
// On iPad in non-fullscreen mode, add left padding for system controls
|
||||||
if Constants.isIPad && !Constants.isWindowFullscreen {
|
if Constants.isIPad && !Constants.isWindowFullscreen {
|
||||||
return Constants.iPadSystemControlsWidth + 15
|
return Constants.iPadSystemControlsWidth + 15
|
||||||
@@ -28,7 +28,7 @@ struct VideoDetailsOverlay: View {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
private var overlayLeadingPadding: CGFloat {
|
private var overlayLeadingPadding: Double {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ final class MPVOGLView: GLKView {
|
|||||||
glGetIntegerv(UInt32(GL_FRAMEBUFFER_BINDING), &defaultFBO!)
|
glGetIntegerv(UInt32(GL_FRAMEBUFFER_BINDING), &defaultFBO!)
|
||||||
|
|
||||||
// Ensure the framebuffer is valid
|
// Ensure the framebuffer is valid
|
||||||
guard defaultFBO != nil && defaultFBO! != 0 else {
|
guard defaultFBO != nil, defaultFBO! != 0 else {
|
||||||
logger.error("Invalid framebuffer ID.")
|
logger.error("Invalid framebuffer ID.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,10 +64,10 @@ struct PlaybackSettings: View {
|
|||||||
.padding(.vertical, 10)
|
.padding(.vertical, 10)
|
||||||
|
|
||||||
if player.activeBackend == .mpv || !player.avPlayerUsesSystemControls {
|
if player.activeBackend == .mpv || !player.avPlayerUsesSystemControls {
|
||||||
HStack(alignment: .center) {
|
HStack {
|
||||||
controlsHeader("Rate".localized())
|
controlsHeader("Rate".localized())
|
||||||
Spacer()
|
Spacer()
|
||||||
HStack(alignment: .center, spacing: rateButtonsSpacing) {
|
HStack(spacing: rateButtonsSpacing) {
|
||||||
decreaseRateButton
|
decreaseRateButton
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
.focused($focusedField, equals: .decreaseRate)
|
.focused($focusedField, equals: .decreaseRate)
|
||||||
@@ -243,18 +243,18 @@ struct PlaybackSettings: View {
|
|||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
Image(systemName: "plus")
|
Image(systemName: "plus")
|
||||||
.imageScale(.large)
|
.imageScale(.large)
|
||||||
.frame(width: 16, height: 16)
|
.frame(width: 16, height: 16)
|
||||||
#else
|
#else
|
||||||
Label("Increase rate", systemImage: "plus")
|
Label("Increase rate", systemImage: "plus")
|
||||||
.imageScale(.large)
|
.imageScale(.large)
|
||||||
.labelStyle(.iconOnly)
|
.labelStyle(.iconOnly)
|
||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.accentColor)
|
||||||
.padding(12)
|
.padding(12)
|
||||||
.frame(width: 40, height: 40)
|
.frame(width: 40, height: 40)
|
||||||
.background(RoundedRectangle(cornerRadius: 4).strokeBorder(Color.accentColor, lineWidth: 1))
|
.background(RoundedRectangle(cornerRadius: 4).strokeBorder(Color.accentColor, lineWidth: 1))
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
@@ -280,18 +280,18 @@ struct PlaybackSettings: View {
|
|||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
Image(systemName: "minus")
|
Image(systemName: "minus")
|
||||||
.imageScale(.large)
|
.imageScale(.large)
|
||||||
.frame(width: 16, height: 16)
|
.frame(width: 16, height: 16)
|
||||||
#else
|
#else
|
||||||
Label("Decrease rate", systemImage: "minus")
|
Label("Decrease rate", systemImage: "minus")
|
||||||
.imageScale(.large)
|
.imageScale(.large)
|
||||||
.labelStyle(.iconOnly)
|
.labelStyle(.iconOnly)
|
||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.accentColor)
|
||||||
.padding(12)
|
.padding(12)
|
||||||
.frame(width: 40, height: 40)
|
.frame(width: 40, height: 40)
|
||||||
.background(RoundedRectangle(cornerRadius: 4).strokeBorder(Color.accentColor, lineWidth: 1))
|
.background(RoundedRectangle(cornerRadius: 4).strokeBorder(Color.accentColor, lineWidth: 1))
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import Foundation
|
|||||||
player.avPlayerBackend.playerLayer
|
player.avPlayerBackend.playerLayer
|
||||||
}
|
}
|
||||||
|
|
||||||
override init(frame frameRect: NSRect) {
|
override init(frame frameRect: CGRect) {
|
||||||
super.init(frame: frameRect)
|
super.init(frame: frameRect)
|
||||||
wantsLayer = true
|
wantsLayer = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ struct StreamControl: View {
|
|||||||
ForEach(kinds, id: \.self) { key in
|
ForEach(kinds, id: \.self) { key in
|
||||||
ForEach(availableStreamsByKind[key] ?? []) { stream in
|
ForEach(availableStreamsByKind[key] ?? []) { stream in
|
||||||
Text(stream.description)
|
Text(stream.description)
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.truncationMode(.middle)
|
.truncationMode(.middle)
|
||||||
#endif
|
#endif
|
||||||
.tag(Stream?.some(stream))
|
.tag(Stream?.some(stream))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -256,11 +256,7 @@ struct CommentView: View {
|
|||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
struct TextSelectionModifier: ViewModifier {
|
struct TextSelectionModifier: ViewModifier {
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
if #available(macOS 12.0, *) {
|
content.textSelection(.enabled)
|
||||||
content.textSelection(.enabled)
|
|
||||||
} else {
|
|
||||||
content
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -58,30 +58,19 @@ struct VideoDescription: View {
|
|||||||
|
|
||||||
@ViewBuilder var textDescription: some View {
|
@ViewBuilder var textDescription: some View {
|
||||||
#if canImport(AppKit)
|
#if canImport(AppKit)
|
||||||
if #available(macOS 12.0, *) {
|
DescriptionWithLinks(description: description, detailsSize: detailsSize)
|
||||||
DescriptionWithLinks(description: description, detailsSize: detailsSize)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.lineLimit(expand ? 500 : collapsedLinesDescription)
|
||||||
.lineLimit(expand ? 500 : collapsedLinesDescription)
|
.textSelection(.enabled)
|
||||||
.textSelection(.enabled)
|
.multilineTextAlignment(.leading)
|
||||||
.multilineTextAlignment(.leading)
|
.font(.system(size: 14))
|
||||||
.font(.system(size: 14))
|
.lineSpacing(3)
|
||||||
.lineSpacing(3)
|
.allowsHitTesting(expand)
|
||||||
.allowsHitTesting(expand)
|
|
||||||
} else {
|
|
||||||
Text(description)
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.lineLimit(expand ? 500 : collapsedLinesDescription)
|
|
||||||
.multilineTextAlignment(.leading)
|
|
||||||
.font(.system(size: 14))
|
|
||||||
.lineSpacing(3)
|
|
||||||
.allowsHitTesting(expand)
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// If possibe convert URLs to clickable links
|
// If possibe convert URLs to clickable links
|
||||||
#if canImport(AppKit)
|
#if canImport(AppKit)
|
||||||
@available(macOS 12.0, *)
|
|
||||||
struct DescriptionWithLinks: View {
|
struct DescriptionWithLinks: View {
|
||||||
let description: String
|
let description: String
|
||||||
let detailsSize: CGSize?
|
let detailsSize: CGSize?
|
||||||
|
|||||||
@@ -314,17 +314,17 @@ struct VideoPlayerView: View {
|
|||||||
)
|
)
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
// TODO: Check whether this is needed on macOS.
|
// TODO: Check whether this is needed on macOS.
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
if player.presentingPlayer {
|
if player.presentingPlayer {
|
||||||
player.setNeedsDrawing(true)
|
player.setNeedsDrawing(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
.id(player.currentVideo?.cacheKey)
|
.id(player.currentVideo?.cacheKey)
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
.offset(y: detailViewDragOffset)
|
.offset(y: detailViewDragOffset)
|
||||||
.gesture(detailsDragGesture)
|
.gesture(detailsDragGesture)
|
||||||
} else {
|
} else {
|
||||||
VStack {}
|
VStack {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,9 +151,9 @@ struct PlaylistsView: View {
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
|
||||||
model.load()
|
model.load()
|
||||||
loadResource()
|
loadResource()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
.background(
|
.background(
|
||||||
|
|||||||
@@ -648,23 +648,23 @@ struct SearchView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
private func searchFieldWidth(_ viewWidth: Double) -> Double {
|
private func searchFieldWidth(_ viewWidth: Double) -> Double {
|
||||||
// Base padding for internal SearchTextField padding (16pt each side = 32 total)
|
// Base padding for internal SearchTextField padding (16pt each side = 32 total)
|
||||||
var totalDeduction: Double = 32
|
var totalDeduction: Double = 32
|
||||||
|
|
||||||
// Add space for trailing menu button
|
// Add space for trailing menu button
|
||||||
totalDeduction += 44
|
|
||||||
|
|
||||||
// Add space for sidebar toggle button if in sidebar navigation style
|
|
||||||
if navigationStyle == .sidebar {
|
|
||||||
totalDeduction += 44
|
totalDeduction += 44
|
||||||
|
|
||||||
|
// Add space for sidebar toggle button if in sidebar navigation style
|
||||||
|
if navigationStyle == .sidebar {
|
||||||
|
totalDeduction += 44
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimum width to ensure usability
|
||||||
|
let minWidth: Double = 200
|
||||||
|
|
||||||
|
return max(minWidth, viewWidth - totalDeduction)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minimum width to ensure usability
|
|
||||||
let minWidth: Double = 200
|
|
||||||
|
|
||||||
return max(minWidth, viewWidth - totalDeduction)
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var shouldDisplayHeader: Bool {
|
var shouldDisplayHeader: Bool {
|
||||||
|
|||||||
@@ -151,52 +151,52 @@ struct PlayerControlsSettings: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
// Custom implementation for tvOS to avoid focus overlay
|
// Custom implementation for tvOS to avoid focus overlay
|
||||||
return VStack(alignment: .leading, spacing: 0) {
|
return VStack(alignment: .leading, spacing: 0) {
|
||||||
Text("System controls buttons")
|
Text("System controls buttons")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.padding(.vertical, 8)
|
.padding(.vertical, 8)
|
||||||
|
|
||||||
Button(action: { systemControlsCommands = .seek }) {
|
Button(action: { systemControlsCommands = .seek }) {
|
||||||
HStack {
|
HStack {
|
||||||
Text(labelText("Seek".localized()))
|
Text(labelText("Seek".localized()))
|
||||||
Spacer()
|
Spacer()
|
||||||
if systemControlsCommands == .seek {
|
if systemControlsCommands == .seek {
|
||||||
Image(systemName: "checkmark")
|
Image(systemName: "checkmark")
|
||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.accentColor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
.contentShape(Rectangle())
|
.buttonStyle(.plain)
|
||||||
}
|
.padding(.vertical, 4)
|
||||||
.buttonStyle(.plain)
|
|
||||||
.padding(.vertical, 4)
|
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
systemControlsCommands = .restartAndAdvanceToNext
|
systemControlsCommands = .restartAndAdvanceToNext
|
||||||
player.updateRemoteCommandCenter()
|
player.updateRemoteCommandCenter()
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Text(labelText("Restart/Play next".localized()))
|
Text(labelText("Restart/Play next".localized()))
|
||||||
Spacer()
|
Spacer()
|
||||||
if systemControlsCommands == .restartAndAdvanceToNext {
|
if systemControlsCommands == .restartAndAdvanceToNext {
|
||||||
Image(systemName: "checkmark")
|
Image(systemName: "checkmark")
|
||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.accentColor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
.contentShape(Rectangle())
|
.buttonStyle(.plain)
|
||||||
|
.padding(.vertical, 4)
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
|
||||||
.padding(.vertical, 4)
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
return Picker("System controls buttons", selection: $systemControlsCommands) {
|
return Picker("System controls buttons", selection: $systemControlsCommands) {
|
||||||
Text(labelText("Seek".localized())).tag(SystemControlsCommands.seek)
|
Text(labelText("Seek".localized())).tag(SystemControlsCommands.seek)
|
||||||
Text(labelText("Restart/Play next".localized())).tag(SystemControlsCommands.restartAndAdvanceToNext)
|
Text(labelText("Restart/Play next".localized())).tag(SystemControlsCommands.restartAndAdvanceToNext)
|
||||||
}
|
}
|
||||||
.onChange(of: systemControlsCommands) { _ in
|
.onChange(of: systemControlsCommands) { _ in
|
||||||
player.updateRemoteCommandCenter()
|
player.updateRemoteCommandCenter()
|
||||||
}
|
}
|
||||||
.modifier(SettingsPickerModifier())
|
.modifier(SettingsPickerModifier())
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -242,11 +242,7 @@ struct QualityProfileForm: View {
|
|||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
let list = filteredFormatList
|
let list = filteredFormatList
|
||||||
|
|
||||||
if #available(macOS 12.0, *) {
|
list.listStyle(.inset(alternatesRowBackgrounds: true))
|
||||||
list.listStyle(.inset(alternatesRowBackgrounds: true))
|
|
||||||
} else {
|
|
||||||
list.listStyle(.inset)
|
|
||||||
}
|
|
||||||
Spacer()
|
Spacer()
|
||||||
#else
|
#else
|
||||||
filteredFormatList
|
filteredFormatList
|
||||||
|
|||||||
@@ -183,17 +183,10 @@ struct QualitySettings: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
if #available(macOS 12.0, *) {
|
List {
|
||||||
List {
|
list
|
||||||
list
|
|
||||||
}
|
|
||||||
.listStyle(.inset(alternatesRowBackgrounds: true))
|
|
||||||
} else {
|
|
||||||
List {
|
|
||||||
list
|
|
||||||
}
|
|
||||||
.listStyle(.inset)
|
|
||||||
}
|
}
|
||||||
|
.listStyle(.inset(alternatesRowBackgrounds: true))
|
||||||
#else
|
#else
|
||||||
list
|
list
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
struct TVOSPlainToggleStyle: ToggleStyle {
|
struct TVOSPlainToggleStyle: ToggleStyle {
|
||||||
func makeBody(configuration: Configuration) -> some View {
|
func makeBody(configuration: Configuration) -> some View {
|
||||||
Button(action: { configuration.isOn.toggle() }) {
|
Button(action: { configuration.isOn.toggle() }) {
|
||||||
HStack {
|
HStack {
|
||||||
configuration.label
|
configuration.label
|
||||||
Spacer()
|
Spacer()
|
||||||
Image(systemName: configuration.isOn ? "checkmark.circle.fill" : "circle")
|
Image(systemName: configuration.isOn ? "checkmark.circle.fill" : "circle")
|
||||||
.foregroundColor(configuration.isOn ? .accentColor : .secondary)
|
.foregroundColor(configuration.isOn ? .accentColor : .secondary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -188,19 +188,19 @@ struct ChannelsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
struct CompactListRowModifier: ViewModifier {
|
struct CompactListRowModifier: ViewModifier {
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
// swiftlint:disable:next deployment_target
|
// swiftlint:disable:next deployment_target
|
||||||
if #available(iOS 15.0, *) {
|
if #available(iOS 15.0, *) {
|
||||||
content
|
content
|
||||||
.listRowSpacing(0)
|
.listRowSpacing(0)
|
||||||
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
|
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
|
||||||
} else {
|
} else {
|
||||||
content
|
content
|
||||||
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
|
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct ChannelsView_Previews: PreviewProvider {
|
struct ChannelsView_Previews: PreviewProvider {
|
||||||
|
|||||||
@@ -111,76 +111,76 @@ struct FeedView: View {
|
|||||||
Text("Channels")
|
Text("Channels")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
List(selection: $selectedChannel) {
|
List(selection: $selectedChannel) {
|
||||||
|
Button(action: {
|
||||||
|
self.selectedChannel = nil
|
||||||
|
self.feedChannelsViewVisible = false
|
||||||
|
}) {
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
Image(systemName: RecentsModel.symbolSystemImage("A"))
|
||||||
|
.imageScale(.large)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
.frame(width: 35, height: 35)
|
||||||
|
Text("All")
|
||||||
|
Spacer()
|
||||||
|
feedCount.unwatchedText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.all)
|
||||||
|
.background(RoundedRectangle(cornerRadius: 8.0)
|
||||||
|
.fill(self.selectedChannel == nil ? Color.secondary : Color.clear))
|
||||||
|
.font(.caption)
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
.focused(self.$focusedChannel, equals: "all")
|
||||||
|
|
||||||
|
ForEach(channels, id: \.self) { channel in
|
||||||
Button(action: {
|
Button(action: {
|
||||||
self.selectedChannel = nil
|
self.selectedChannel = channel
|
||||||
self.feedChannelsViewVisible = false
|
self.feedChannelsViewVisible = false
|
||||||
}) {
|
}) {
|
||||||
HStack(spacing: 16) {
|
HStack(spacing: 16) {
|
||||||
Image(systemName: RecentsModel.symbolSystemImage("A"))
|
ChannelAvatarView(channel: channel, subscribedBadge: false)
|
||||||
.imageScale(.large)
|
.frame(width: 50, height: 50)
|
||||||
.foregroundColor(.accentColor)
|
Text(channel.name)
|
||||||
.frame(width: 35, height: 35)
|
.lineLimit(1)
|
||||||
Text("All")
|
|
||||||
Spacer()
|
Spacer()
|
||||||
feedCount.unwatchedText
|
if let unwatchedCount = feedCount.unwatchedByChannelText(channel) {
|
||||||
|
unwatchedCount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.all)
|
.padding(.all)
|
||||||
.background(RoundedRectangle(cornerRadius: 8.0)
|
.background(RoundedRectangle(cornerRadius: 8.0)
|
||||||
.fill(self.selectedChannel == nil ? Color.secondary : Color.clear))
|
.fill(self.selectedChannel == channel ? Color.secondary : Color.clear))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
.focused(self.$focusedChannel, equals: "all")
|
.focused(self.$focusedChannel, equals: channel.id)
|
||||||
|
}
|
||||||
ForEach(channels, id: \.self) { channel in
|
|
||||||
Button(action: {
|
|
||||||
self.selectedChannel = channel
|
|
||||||
self.feedChannelsViewVisible = false
|
|
||||||
}) {
|
|
||||||
HStack(spacing: 16) {
|
|
||||||
ChannelAvatarView(channel: channel, subscribedBadge: false)
|
|
||||||
.frame(width: 50, height: 50)
|
|
||||||
Text(channel.name)
|
|
||||||
.lineLimit(1)
|
|
||||||
Spacer()
|
|
||||||
if let unwatchedCount = feedCount.unwatchedByChannelText(channel) {
|
|
||||||
unwatchedCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.all)
|
|
||||||
.background(RoundedRectangle(cornerRadius: 8.0)
|
|
||||||
.fill(self.selectedChannel == channel ? Color.secondary : Color.clear))
|
|
||||||
.font(.caption)
|
|
||||||
.buttonStyle(PlainButtonStyle())
|
|
||||||
.focused(self.$focusedChannel, equals: channel.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.onChange(of: self.focusedChannel) { _ in
|
.onChange(of: self.focusedChannel) { _ in
|
||||||
if self.focusedChannel == "all" {
|
if self.focusedChannel == "all" {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
self.selectedChannel = nil
|
self.selectedChannel = nil
|
||||||
}
|
}
|
||||||
} else if self.focusedChannel == dismiss_channel_list_id {
|
} else if self.focusedChannel == dismiss_channel_list_id {
|
||||||
self.feedChannelsViewVisible = false
|
self.feedChannelsViewVisible = false
|
||||||
} else {
|
} else {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
self.selectedChannel = channels.first {
|
self.selectedChannel = channels.first {
|
||||||
$0.id == self.focusedChannel
|
$0.id == self.focusedChannel
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
guard let selectedChannel = self.selectedChannel else {
|
guard let selectedChannel = self.selectedChannel else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
proxy.scrollTo(selectedChannel, anchor: .top)
|
proxy.scrollTo(selectedChannel, anchor: .top)
|
||||||
}
|
}
|
||||||
.onExitCommand {
|
.onExitCommand {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
self.feedChannelsViewVisible = false
|
self.feedChannelsViewVisible = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,44 +216,28 @@ struct FeedView: View {
|
|||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
|
||||||
feed.loadResources()
|
feed.loadResources()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
var header: some View {
|
var header: some View {
|
||||||
HStack(spacing: 16) {
|
HStack(spacing: 16) {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
if #available(tvOS 17.0, *) {
|
Menu {
|
||||||
Menu {
|
accountsPicker
|
||||||
accountsPicker
|
} label: {
|
||||||
} label: {
|
Label("Channels", systemImage: "filemenu.and.selection")
|
||||||
Label("Channels", systemImage: "filemenu.and.selection")
|
.labelStyle(.iconOnly)
|
||||||
.labelStyle(.iconOnly)
|
.imageScale(.small)
|
||||||
.imageScale(.small)
|
.font(.caption)
|
||||||
.font(.caption)
|
} primaryAction: {
|
||||||
} primaryAction: {
|
withAnimation {
|
||||||
withAnimation {
|
self.feedChannelsViewVisible = true
|
||||||
self.feedChannelsViewVisible = true
|
self.focusedChannel = selectedChannel?.id ?? "all"
|
||||||
self.focusedChannel = selectedChannel?.id ?? "all"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.opacity(feedChannelsViewVisible ? 0 : 1)
|
|
||||||
.frame(minWidth: feedChannelsViewVisible ? 0 : nil, maxWidth: feedChannelsViewVisible ? 0 : nil)
|
|
||||||
} else {
|
|
||||||
Button {
|
|
||||||
withAnimation {
|
|
||||||
self.feedChannelsViewVisible = true
|
|
||||||
self.focusedChannel = selectedChannel?.id ?? "all"
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
Label("Channels", systemImage: "filemenu.and.selection")
|
|
||||||
.labelStyle(.iconOnly)
|
|
||||||
.imageScale(.small)
|
|
||||||
.font(.caption)
|
|
||||||
}
|
|
||||||
.opacity(feedChannelsViewVisible ? 0 : 1)
|
|
||||||
.frame(minWidth: feedChannelsViewVisible ? 0 : nil, maxWidth: feedChannelsViewVisible ? 0 : nil)
|
|
||||||
}
|
}
|
||||||
|
.opacity(feedChannelsViewVisible ? 0 : 1)
|
||||||
|
.frame(minWidth: feedChannelsViewVisible ? 0 : nil, maxWidth: feedChannelsViewVisible ? 0 : nil)
|
||||||
channelHeaderView
|
channelHeaderView
|
||||||
if selectedChannel == nil {
|
if selectedChannel == nil {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|||||||
@@ -58,11 +58,7 @@ struct TrendingCountry: View {
|
|||||||
|
|
||||||
return Group {
|
return Group {
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
if #available(macOS 12.0, *) {
|
list.listStyle(.inset(alternatesRowBackgrounds: true))
|
||||||
list.listStyle(.inset(alternatesRowBackgrounds: true))
|
|
||||||
} else {
|
|
||||||
list.listStyle(.inset)
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
list
|
list
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -78,12 +78,12 @@ struct TrendingView: View {
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
.sheet(isPresented: $presentingCountrySelection) {
|
.sheet(isPresented: $presentingCountrySelection) {
|
||||||
TrendingCountry(selectedCountry: $country)
|
TrendingCountry(selectedCountry: $country)
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.frame(minWidth: 400, minHeight: 400)
|
.frame(minWidth: 400, minHeight: 400)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.background(
|
.background(
|
||||||
Button("Refresh") {
|
Button("Refresh") {
|
||||||
resource.load()
|
resource.load()
|
||||||
.onFailure { self.error = $0 }
|
.onFailure { self.error = $0 }
|
||||||
@@ -131,10 +131,10 @@ struct TrendingView: View {
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
|
||||||
resource.loadIfNeeded()?
|
resource.loadIfNeeded()?
|
||||||
.onFailure { self.error = $0 }
|
.onFailure { self.error = $0 }
|
||||||
.onSuccess { _ in self.error = nil }
|
.onSuccess { _ in self.error = nil }
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ struct ThumbnailView: View {
|
|||||||
|
|
||||||
init(url: URL?) {
|
init(url: URL?) {
|
||||||
self.url = url
|
self.url = url
|
||||||
self.thumbnailExtension = Self.extractExtension(from: url)
|
thumbnailExtension = Self.extractExtension(from: url)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func extractExtension(from url: URL?) -> String? {
|
private static func extractExtension(from url: URL?) -> String? {
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ struct VerticalCells<Header: View>: View {
|
|||||||
.edgesIgnoringSafeArea(navigationStyle == .sidebar ? [] : edgesIgnoringSafeArea)
|
.edgesIgnoringSafeArea(navigationStyle == .sidebar ? [] : edgesIgnoringSafeArea)
|
||||||
#endif
|
#endif
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.background(Color.secondaryBackground)
|
.background(Color.secondaryBackground)
|
||||||
.frame(minWidth: Constants.contentViewMinWidth)
|
.frame(minWidth: Constants.contentViewMinWidth)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -341,6 +341,7 @@ struct ControlsBar_Previews: PreviewProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - View Extension for Conditional Modifiers
|
// MARK: - View Extension for Conditional Modifiers
|
||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func `if`<Transform: View>(_ condition: Bool, transform: (Self) -> Transform) -> some View {
|
func `if`<Transform: View>(_ condition: Bool, transform: (Self) -> Transform) -> some View {
|
||||||
@@ -355,16 +356,40 @@ extension View {
|
|||||||
func applyControlsBackground(enabled: Bool, cornerRadius: Double) -> some View {
|
func applyControlsBackground(enabled: Bool, cornerRadius: Double) -> some View {
|
||||||
if enabled {
|
if enabled {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
if #available(iOS 26.0, *) {
|
if #available(iOS 26.0, *) {
|
||||||
// Use Liquid Glass on iOS 26+
|
// Use Liquid Glass on iOS 26+
|
||||||
self.glassEffect(
|
self.glassEffect(
|
||||||
.regular.interactive(),
|
.regular.interactive(),
|
||||||
in: .rect(cornerRadius: cornerRadius)
|
in: .rect(cornerRadius: cornerRadius)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Fallback to ultraThinMaterial
|
// Fallback to ultraThinMaterial
|
||||||
|
// swiftlint:disable:next deployment_target
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
self
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
|
.fill(.ultraThinMaterial)
|
||||||
|
)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
|
.stroke(Color("ControlsBorderColor"), lineWidth: 0.5)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
background(
|
||||||
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
|
.fill(Color.gray.opacity(0.3))
|
||||||
|
)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
|
.stroke(Color("ControlsBorderColor"), lineWidth: 0.5)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// Fallback to ultraThinMaterial for macOS and tvOS
|
||||||
// swiftlint:disable:next deployment_target
|
// swiftlint:disable:next deployment_target
|
||||||
if #available(iOS 15.0, *) {
|
if #available(macOS 12.0, tvOS 15.0, *) {
|
||||||
self
|
self
|
||||||
.background(
|
.background(
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
@@ -375,33 +400,7 @@ extension View {
|
|||||||
.stroke(Color("ControlsBorderColor"), lineWidth: 0.5)
|
.stroke(Color("ControlsBorderColor"), lineWidth: 0.5)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
self
|
background(
|
||||||
.background(
|
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
|
||||||
.fill(Color.gray.opacity(0.3))
|
|
||||||
)
|
|
||||||
.overlay(
|
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
|
||||||
.stroke(Color("ControlsBorderColor"), lineWidth: 0.5)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// Fallback to ultraThinMaterial for macOS and tvOS
|
|
||||||
// swiftlint:disable:next deployment_target
|
|
||||||
if #available(macOS 12.0, tvOS 15.0, *) {
|
|
||||||
self
|
|
||||||
.background(
|
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
|
||||||
.fill(.ultraThinMaterial)
|
|
||||||
)
|
|
||||||
.overlay(
|
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
|
||||||
.stroke(Color("ControlsBorderColor"), lineWidth: 0.5)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
self
|
|
||||||
.background(
|
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
.fill(Color.gray.opacity(0.3))
|
.fill(Color.gray.opacity(0.3))
|
||||||
)
|
)
|
||||||
@@ -409,10 +408,10 @@ extension View {
|
|||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
.stroke(Color("ControlsBorderColor"), lineWidth: 0.5)
|
.stroke(Color("ControlsBorderColor"), lineWidth: 0.5)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
self.background(Color.clear)
|
background(Color.clear)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,10 +81,10 @@ struct PopularView: View {
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
|
||||||
resource?.loadIfNeeded()?
|
resource?.loadIfNeeded()?
|
||||||
.onFailure { self.error = $0 }
|
.onFailure { self.error = $0 }
|
||||||
.onSuccess { _ in self.error = nil }
|
.onSuccess { _ in self.error = nil }
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,28 +4,28 @@ import SwiftUI
|
|||||||
struct SettingsPickerModifier: ViewModifier {
|
struct SettingsPickerModifier: ViewModifier {
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
content
|
content
|
||||||
.pickerStyle(.inline)
|
.pickerStyle(.inline)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
// Force refresh to apply button style to picker options
|
// Force refresh to apply button style to picker options
|
||||||
}
|
}
|
||||||
#elseif os(iOS)
|
#elseif os(iOS)
|
||||||
content
|
content
|
||||||
.pickerStyle(.automatic)
|
.pickerStyle(.automatic)
|
||||||
#else
|
#else
|
||||||
content
|
content
|
||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
.pickerStyle(.menu)
|
.pickerStyle(.menu)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
// Extension to help remove picker row backgrounds
|
// Extension to help remove picker row backgrounds
|
||||||
extension View {
|
extension View {
|
||||||
func pickerRowStyle() -> some View {
|
func pickerRowStyle() -> some View {
|
||||||
self.buttonStyle(.plain)
|
buttonStyle(.plain)
|
||||||
.listRowBackground(Color.clear)
|
.listRowBackground(Color.clear)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -73,15 +73,15 @@ struct YatteeApp: App {
|
|||||||
)
|
)
|
||||||
#else
|
#else
|
||||||
.onReceive(
|
.onReceive(
|
||||||
NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)
|
NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)
|
||||||
) { _ in
|
) { _ in
|
||||||
player.handleEnterForeground()
|
player.handleEnterForeground()
|
||||||
}
|
}
|
||||||
.onReceive(
|
.onReceive(
|
||||||
NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)
|
NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)
|
||||||
) { _ in
|
) { _ in
|
||||||
player.handleEnterBackground()
|
player.handleEnterBackground()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.handlesExternalEvents(preferring: Set(["*"]), allowing: Set(["*"]))
|
.handlesExternalEvents(preferring: Set(["*"]), allowing: Set(["*"]))
|
||||||
|
|||||||
@@ -71,11 +71,7 @@ struct InstancesSettings: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if #available(macOS 12.0, *) {
|
list.listStyle(.inset(alternatesRowBackgrounds: true))
|
||||||
list.listStyle(.inset(alternatesRowBackgrounds: true))
|
|
||||||
} else {
|
|
||||||
list.listStyle(.inset)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if selectedInstance != nil, selectedInstance.app.hasFrontendURL {
|
if selectedInstance != nil, selectedInstance.app.hasFrontendURL {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ final class VideoLayer: CAOpenGLLayer {
|
|||||||
displayTime _: UnsafePointer<CVTimeStamp>?
|
displayTime _: UnsafePointer<CVTimeStamp>?
|
||||||
) {
|
) {
|
||||||
needsRedraw = false
|
needsRedraw = false
|
||||||
|
|
||||||
var i: GLint = 0
|
var i: GLint = 0
|
||||||
var flip: CInt = 1
|
var flip: CInt = 1
|
||||||
var ditherDepth = 8
|
var ditherDepth = 8
|
||||||
@@ -103,7 +103,7 @@ final class VideoLayer: CAOpenGLLayer {
|
|||||||
needsRedraw = true
|
needsRedraw = true
|
||||||
super.display()
|
super.display()
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestRedraw() {
|
func requestRedraw() {
|
||||||
// Called from MPV's glUpdate callback - use setNeedsDisplay for efficient batching
|
// Called from MPV's glUpdate callback - use setNeedsDisplay for efficient batching
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
|||||||
Reference in New Issue
Block a user