From 6a2aca83e613c53ed2ea78148f074e086a646a7c Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 30 Sep 2026 17:16:15 +0200 Subject: [PATCH 1/5] Localization Signed-off-by: Milen Pivchev --- .../Content/Video/VLC/NCVideoVLCViewController.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/iOSClient/Viewer/NCViewerMedia/Content/Video/VLC/NCVideoVLCViewController.swift b/iOSClient/Viewer/NCViewerMedia/Content/Video/VLC/NCVideoVLCViewController.swift index aed7cc35b0..6062613a22 100644 --- a/iOSClient/Viewer/NCViewerMedia/Content/Video/VLC/NCVideoVLCViewController.swift +++ b/iOSClient/Viewer/NCViewerMedia/Content/Video/VLC/NCVideoVLCViewController.swift @@ -1083,9 +1083,10 @@ final class NCVideoVLCViewController: UIViewController { return nil } + // VLC reports the "Disable" track as index -1. return NCVideoTrackMenuItem( index: trackIndex, - title: title, + title: trackIndex == -1 ? NSLocalizedString("_disable_", comment: "") : title, isSelected: currentIndex == Int(trackIndex) ) } From 132afb0ccfe3f87705d0d1856e1a04469d33ed3f Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 30 Sep 2026 17:22:02 +0200 Subject: [PATCH 2/5] indicator Signed-off-by: Milen Pivchev --- .../NCViewerMedia/Content/Video/NCVideoPlaybackCoverView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoPlaybackCoverView.swift b/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoPlaybackCoverView.swift index 5d45dd5ae5..c73f94ffd4 100644 --- a/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoPlaybackCoverView.swift +++ b/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoPlaybackCoverView.swift @@ -58,7 +58,7 @@ struct NCVideoPlaybackCoverView: View { if isLoading || isLaunchingPlayback { ProgressView() .controlSize(.large) - .tint(.white) + .tint(.primary.opacity(0.6)) .transition(.opacity) } else { Image(systemName: "play.fill") From 2d632f85d3d6be79800757be13b70e4a72acb7ae Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 30 Sep 2026 18:24:15 +0200 Subject: [PATCH 3/5] Fix visibility Signed-off-by: Milen Pivchev --- .../AVPlayer/NCVideoAVPlayerPresenter.swift | 5 +-- .../Content/Video/NCVideoControlsView.swift | 38 +++++-------------- .../Video/NCVideoPlaybackCoverView.swift | 21 +++------- .../Video/VLC/NCVideoVLCPresenter.swift | 5 +-- 4 files changed, 19 insertions(+), 50 deletions(-) diff --git a/iOSClient/Viewer/NCViewerMedia/Content/Video/AVPlayer/NCVideoAVPlayerPresenter.swift b/iOSClient/Viewer/NCViewerMedia/Content/Video/AVPlayer/NCVideoAVPlayerPresenter.swift index 594db240c4..2da6091b0e 100644 --- a/iOSClient/Viewer/NCViewerMedia/Content/Video/AVPlayer/NCVideoAVPlayerPresenter.swift +++ b/iOSClient/Viewer/NCViewerMedia/Content/Video/AVPlayer/NCVideoAVPlayerPresenter.swift @@ -137,10 +137,9 @@ enum NCVideoAVPlayerPresenter { navigationController.modalPresentationStyle = .fullScreen navigationController.navigationBar.prefersLargeTitles = false - navigationController.navigationBar.barStyle = .black - navigationController.navigationBar.tintColor = .white + navigationController.navigationBar.tintColor = .label navigationController.navigationBar.titleTextAttributes = [ - .foregroundColor: UIColor.white + .foregroundColor: UIColor.label ] if !playbackStartReason.shouldShowControlsOnStart { diff --git a/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoControlsView.swift b/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoControlsView.swift index 9c40a6799d..132f3cd438 100644 --- a/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoControlsView.swift +++ b/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoControlsView.swift @@ -521,9 +521,8 @@ private struct NCVideoControlsSwiftUIView: View { HStack(spacing: NCVideoControlsView.topActionsSpacing) { Button(action: onToggleRepeat) { topActionIcon( - systemName: "repeat.1", - pointSize: 17, - isActive: state.isRepeatEnabled + systemName: state.isRepeatEnabled ? "repeat.1.circle.fill" : "repeat.1", + pointSize: 17 ) } .buttonStyle(.plain) @@ -532,8 +531,7 @@ private struct NCVideoControlsSwiftUIView: View { Button(action: onToggleAutoAdvance) { topActionIcon( systemName: state.isAutoAdvanceEnabled ? "forward.end.fill" : "forward.end", - pointSize: 17, - isActive: state.isAutoAdvanceEnabled + pointSize: 17 ) } .buttonStyle(.plain) @@ -557,7 +555,6 @@ private struct NCVideoControlsSwiftUIView: View { width: NCVideoControlsView.topActionsButtonSize, height: NCVideoControlsView.topActionsButtonSize ) - .videoControlIconShadow() .controlGlassBackground(shape: Circle()) case .vlcTracks: @@ -663,13 +660,11 @@ private struct NCVideoControlsSwiftUIView: View { private func topActionIcon( systemName: String, - pointSize: CGFloat, - isActive: Bool = false + pointSize: CGFloat ) -> some View { Image(systemName: systemName) .font(.system(size: pointSize, weight: .regular)) - .foregroundStyle(isActive ? Color.accentColor : .white) - .videoControlIconShadow() + .foregroundStyle(.primary) .frame( width: NCVideoControlsView.topActionsButtonSize, height: NCVideoControlsView.topActionsButtonSize @@ -693,8 +688,7 @@ private struct NCVideoControlsSwiftUIView: View { } label: { Image(systemName: systemName) .font(.system(size: pointSize, weight: .regular)) - .foregroundStyle(.white) - .videoControlIconShadow() + .foregroundStyle(.primary) .frame(width: size, height: size) .controlGlassBackground(shape: Circle()) } @@ -707,8 +701,7 @@ private struct NCVideoControlsSwiftUIView: View { private func timeLabel(_ text: String) -> some View { Text(text) .font(.system(size: 15, weight: .medium, design: .rounded).monospacedDigit()) - .foregroundStyle(.gray) - .videoControlIconShadow() + .foregroundStyle(.secondary) .lineLimit(1) .minimumScaleFactor(0.85) } @@ -720,8 +713,8 @@ private struct NCVideoAirPlayRoutePickerView: UIViewRepresentable { func makeUIView(context: Context) -> AVRoutePickerView { let routePickerView = AVRoutePickerView() routePickerView.backgroundColor = .clear - routePickerView.tintColor = .white - routePickerView.activeTintColor = .white + routePickerView.tintColor = .label + routePickerView.activeTintColor = .label routePickerView.prioritizesVideoDevices = true return routePickerView } @@ -758,23 +751,12 @@ private extension View { ) } else { self - .background(.white.opacity(0.92)) + .background(.regularMaterial) .clipShape(shape) } } } -private extension View { - func videoControlIconShadow() -> some View { - shadow( - color: .black.opacity(0.5), - radius: 2.5, - x: 0, - y: 1 - ) - } -} - // MARK: - Preview #Preview("Video Controls") { diff --git a/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoPlaybackCoverView.swift b/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoPlaybackCoverView.swift index c73f94ffd4..0930415dd8 100644 --- a/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoPlaybackCoverView.swift +++ b/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoPlaybackCoverView.swift @@ -63,14 +63,14 @@ struct NCVideoPlaybackCoverView: View { } else { Image(systemName: "play.fill") .font(.system(size: 36, weight: .regular)) - .foregroundStyle(isPlayEnabled ? .white : .black.opacity(0.35)) - .videoControlIconShadow() + .foregroundStyle(isPlayEnabled ? .primary : .tertiary) .transition(.opacity) } } .frame(width: 62, height: 62) - .coverPlayButtonBackground(isEnabled: isPlayEnabled) + .coverPlayButtonBackground() } + .buttonStyle(.plain) .disabled(!isPlayEnabled || isLoading || isLaunchingPlayback) .scaleEffect(isLaunchingPlayback ? 1.06 : 1) .animation(.easeInOut(duration: 0.18), value: isLoading) @@ -108,7 +108,7 @@ struct NCVideoPlaybackCoverView: View { private extension View { @ViewBuilder - func coverPlayButtonBackground(isEnabled: Bool) -> some View { + func coverPlayButtonBackground() -> some View { if #available(iOS 26.0, *) { self .glassEffect(.regular, in: .circle) @@ -130,23 +130,12 @@ private extension View { ) } else { self - .background(.white.opacity(isEnabled ? 0.92 : 0.45)) + .background(.regularMaterial) .clipShape(Circle()) } } } -private extension View { - func videoControlIconShadow() -> some View { - shadow( - color: .black.opacity(0.5), - radius: 2.5, - x: 0, - y: 1 - ) - } -} - #Preview("Video Playback Cover") { NCVideoPlaybackCoverView( previewURL: NCVideoPlaybackCoverPreviewImage.url, diff --git a/iOSClient/Viewer/NCViewerMedia/Content/Video/VLC/NCVideoVLCPresenter.swift b/iOSClient/Viewer/NCViewerMedia/Content/Video/VLC/NCVideoVLCPresenter.swift index 4a17acde92..90f1b09a0d 100644 --- a/iOSClient/Viewer/NCViewerMedia/Content/Video/VLC/NCVideoVLCPresenter.swift +++ b/iOSClient/Viewer/NCViewerMedia/Content/Video/VLC/NCVideoVLCPresenter.swift @@ -141,10 +141,9 @@ enum NCVideoVLCPresenter { navigationController.modalPresentationStyle = .fullScreen navigationController.navigationBar.prefersLargeTitles = false - navigationController.navigationBar.barStyle = .black - navigationController.navigationBar.tintColor = .white + navigationController.navigationBar.tintColor = .label navigationController.navigationBar.titleTextAttributes = [ - .foregroundColor: UIColor.white + .foregroundColor: UIColor.label ] if !playbackStartReason.shouldShowControlsOnStart { From 2eb64512e44c3c741bbe7db950b41c3c9a5a37ef Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 2 Oct 2026 14:28:28 +0200 Subject: [PATCH 4/5] Remove custom glass look Signed-off-by: Milen Pivchev --- .../Content/Video/NCVideoControlsView.swift | 23 ++++--------------- .../Video/NCVideoPlaybackCoverView.swift | 18 +-------------- 2 files changed, 5 insertions(+), 36 deletions(-) diff --git a/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoControlsView.swift b/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoControlsView.swift index 132f3cd438..260d82b70c 100644 --- a/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoControlsView.swift +++ b/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoControlsView.swift @@ -513,7 +513,7 @@ private struct NCVideoControlsSwiftUIView: View { } .padding(.horizontal, 18) .frame(maxWidth: .infinity, maxHeight: .infinity) - .controlGlassBackground(shape: Capsule()) + .controlGlassBackground(shape: Capsule(), isInteractive: false) .contentShape(Capsule()) } @@ -728,27 +728,12 @@ private struct NCVideoAirPlayRoutePickerView: UIViewRepresentable { private extension View { @ViewBuilder func controlGlassBackground( - shape: BackgroundShape + shape: BackgroundShape, + isInteractive: Bool = true ) -> some View { if #available(iOS 26.0, *) { self - .glassEffect(.regular, in: shape) - .overlay { - shape - .stroke(.white.opacity(0.58), lineWidth: 1.2) - } - .overlay { - shape - .stroke(.white.opacity(0.20), lineWidth: 4) - .blur(radius: 2) - .mask(shape) - } - .shadow( - color: .black.opacity(0.18), - radius: 14, - x: 0, - y: 4 - ) + .glassEffect(.regular.interactive(isInteractive), in: shape) } else { self .background(.regularMaterial) diff --git a/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoPlaybackCoverView.swift b/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoPlaybackCoverView.swift index 0930415dd8..041939e97f 100644 --- a/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoPlaybackCoverView.swift +++ b/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoPlaybackCoverView.swift @@ -111,23 +111,7 @@ private extension View { func coverPlayButtonBackground() -> some View { if #available(iOS 26.0, *) { self - .glassEffect(.regular, in: .circle) - .overlay { - Circle() - .stroke(.white.opacity(0.58), lineWidth: 1.2) - } - .overlay { - Circle() - .stroke(.white.opacity(0.20), lineWidth: 4) - .blur(radius: 2) - .mask(Circle()) - } - .shadow( - color: .black.opacity(0.18), - radius: 14, - x: 0, - y: 4 - ) + .glassEffect(.regular.interactive(), in: .circle) } else { self .background(.regularMaterial) From 6858e70a27a1d3d319bdbd0a7735619e460bec12 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Mon, 5 Oct 2026 12:05:08 +0200 Subject: [PATCH 5/5] WIP Signed-off-by: Milen Pivchev --- .../Audio/NCAudioViewerContentView.swift | 51 +++---------------- .../Video/NCVideoPlaybackCoverView.swift | 7 +-- 2 files changed, 10 insertions(+), 48 deletions(-) diff --git a/iOSClient/Viewer/NCViewerMedia/Content/Audio/NCAudioViewerContentView.swift b/iOSClient/Viewer/NCViewerMedia/Content/Audio/NCAudioViewerContentView.swift index bde50e243c..eeb747d428 100644 --- a/iOSClient/Viewer/NCViewerMedia/Content/Audio/NCAudioViewerContentView.swift +++ b/iOSClient/Viewer/NCViewerMedia/Content/Audio/NCAudioViewerContentView.swift @@ -159,9 +159,9 @@ struct NCAudioViewerContentView: View { Button { model.restart() } label: { - Image(systemName: "backward.end.circle") + Image(systemName: "backward.end.circle.fill") .font(.system(size: sideButtonSize, weight: .regular)) - .foregroundStyle(mutedForegroundStyle) + .foregroundStyle(primaryForegroundStyle) } .buttonStyle(.plain) .disabled(!isSelected || model.duration <= 0) @@ -177,8 +177,7 @@ struct NCAudioViewerContentView: View { VStack { HStack(spacing: 8) { audioPlaybackOptionButton( - systemName: "repeat.1", - isActive: playbackOptions.isRepeatEnabled, + systemName: playbackOptions.isRepeatEnabled ? "repeat.1.circle.fill" : "repeat.1", accessibilityLabel: "_repeat_current_media_" ) { playbackOptions.toggleRepeat() @@ -186,7 +185,6 @@ struct NCAudioViewerContentView: View { audioPlaybackOptionButton( systemName: playbackOptions.isAutoAdvanceEnabled ? "forward.end.fill" : "forward.end", - isActive: playbackOptions.isAutoAdvanceEnabled, accessibilityLabel: "_play_next_media_automatically_" ) { playbackOptions.toggleAutoAdvance() @@ -270,20 +268,13 @@ struct NCAudioViewerContentView: View { private func audioPlaybackOptionButton( systemName: String, - isActive: Bool, accessibilityLabel: String, action: @escaping () -> Void ) -> some View { Button(action: action) { Image(systemName: systemName) .font(.system(size: 17, weight: .regular)) - .foregroundStyle(isActive ? Color.accentColor : primaryForegroundStyle) - .shadow( - color: .black.opacity(0.35), - radius: 2, - x: 0, - y: 1 - ) + .foregroundStyle(.primary) .frame(width: 38, height: 38) .audioControlGlassBackground(shape: Circle()) } @@ -332,22 +323,6 @@ struct NCAudioViewerContentView: View { } } - private var mutedForegroundStyle: Color { - switch backgroundStyle { - case .black: - return .white.opacity(0.45) - - case .white: - return .black.opacity(0.40) - - case .system: - return .secondary.opacity(0.70) - - case .custom: - return .white.opacity(0.45) - } - } - private var artworkPlaceholderBackground: Color { switch backgroundStyle { case .black: @@ -418,24 +393,10 @@ private extension View { ) -> some View { if #available(iOS 26.0, *) { self - .glassEffect(.regular, in: shape) - .overlay { - shape - .stroke(.white.opacity(0.58), lineWidth: 1.2) - } - .shadow( - color: .black.opacity(0.18), - radius: 14, - x: 0, - y: 4 - ) + .glassEffect(.regular.interactive(), in: shape) } else { self - .background(.ultraThinMaterial, in: shape) - .overlay { - shape - .stroke(.primary.opacity(0.12), lineWidth: 1) - } + .background(.regularMaterial) .clipShape(shape) } } diff --git a/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoPlaybackCoverView.swift b/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoPlaybackCoverView.swift index 041939e97f..e6eb6963d1 100644 --- a/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoPlaybackCoverView.swift +++ b/iOSClient/Viewer/NCViewerMedia/Content/Video/NCVideoPlaybackCoverView.swift @@ -58,19 +58,20 @@ struct NCVideoPlaybackCoverView: View { if isLoading || isLaunchingPlayback { ProgressView() .controlSize(.large) - .tint(.primary.opacity(0.6)) + .tint(.primary) .transition(.opacity) } else { + // Color.primary, not .primary: the default button style resolves .primary against the accent tint. + // The default style stays because .plain fades disabled content, like the loading spinner. Image(systemName: "play.fill") .font(.system(size: 36, weight: .regular)) - .foregroundStyle(isPlayEnabled ? .primary : .tertiary) + .foregroundStyle(Color.primary.opacity(isPlayEnabled ? 1 : 0.35)) .transition(.opacity) } } .frame(width: 62, height: 62) .coverPlayButtonBackground() } - .buttonStyle(.plain) .disabled(!isPlayEnabled || isLoading || isLaunchingPlayback) .scaleEffect(isLaunchingPlayback ? 1.06 : 1) .animation(.easeInOut(duration: 0.18), value: isLoading)