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:
Arkadiusz Fal
2025-11-15 19:42:37 +01:00
parent 97ae843013
commit 5758417293
42 changed files with 400 additions and 453 deletions

View File

@@ -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

View File

@@ -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 [:]
} }

View File

@@ -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

View File

@@ -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")
} }

View File

@@ -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)
} }

View File

@@ -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)
} }

View File

@@ -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
} }

View File

@@ -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)

View File

@@ -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
} }

View File

@@ -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`

View File

@@ -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

View File

@@ -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
} }

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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()
} }

View File

@@ -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
} }

View File

@@ -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

View File

@@ -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
} }

View File

@@ -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)

View File

@@ -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
} }

View File

@@ -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))
} }

View File

@@ -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

View File

@@ -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?

View File

@@ -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 {}
} }

View File

@@ -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(

View File

@@ -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 {

View File

@@ -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
} }

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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()

View File

@@ -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

View File

@@ -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
} }

View File

@@ -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? {

View File

@@ -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
} }

View File

@@ -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)
} }
} }
} }

View File

@@ -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
} }

View File

@@ -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

View File

@@ -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(["*"]))

View File

@@ -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 {

View File

@@ -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