diff --git a/Yattee/Localizable.xcstrings b/Yattee/Localizable.xcstrings index eec7d7d5..e58bf1be 100644 --- a/Yattee/Localizable.xcstrings +++ b/Yattee/Localizable.xcstrings @@ -4953,6 +4953,26 @@ } } }, + "mediaBrowser.unsupportedFile.title" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unsupported file" + } + } + } + }, + "mediaBrowser.unsupportedFile.message %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@ cannot be played. This file type is not supported." + } + } + } + }, "mediaBrowser.sort.dateCreated" : { "localizations" : { "en" : { diff --git a/Yattee/Views/MediaBrowser/MediaBrowserView.swift b/Yattee/Views/MediaBrowser/MediaBrowserView.swift index e6614411..8c230767 100644 --- a/Yattee/Views/MediaBrowser/MediaBrowserView.swift +++ b/Yattee/Views/MediaBrowser/MediaBrowserView.swift @@ -22,6 +22,9 @@ struct MediaBrowserView: View { @State private var sortOrder: MediaBrowserSortOrder @State private var sortAscending: Bool @State private var showViewOptions = false + #if os(tvOS) + @State private var unsupportedFile: MediaFile? + #endif private var listStyle: VideoListStyle { appEnvironment?.settingsManager.listStyle ?? .inset @@ -165,7 +168,13 @@ struct MediaBrowserView: View { } else if file.isPlayable { playableFileRow(for: file) } else { + #if os(tvOS) + MediaFileTVOSUnsupportedButton(onTap: { unsupportedFile = file }) { + MediaFileRow(file: file, sortOrder: sortOrder) + } + #else MediaFileRow(file: file, sortOrder: sortOrder) + #endif } } } @@ -174,6 +183,20 @@ struct MediaBrowserView: View { .padding(.top, 16) } ) + #if os(tvOS) + .alert( + String(localized: "mediaBrowser.unsupportedFile.title"), + isPresented: Binding( + get: { unsupportedFile != nil }, + set: { if !$0 { unsupportedFile = nil } } + ), + presenting: unsupportedFile + ) { _ in + Button(String(localized: "common.ok"), role: .cancel) { unsupportedFile = nil } + } message: { file in + Text(String(localized: "mediaBrowser.unsupportedFile.message \(file.name)")) + } + #endif } @ViewBuilder diff --git a/Yattee/Views/MediaBrowser/MediaFileTapModifier.swift b/Yattee/Views/MediaBrowser/MediaFileTapModifier.swift index 2a740e31..95a52cc6 100644 --- a/Yattee/Views/MediaBrowser/MediaFileTapModifier.swift +++ b/Yattee/Views/MediaBrowser/MediaFileTapModifier.swift @@ -35,6 +35,17 @@ struct MediaFileTVOSTapButton: View { .buttonStyle(.plain) } } + +/// tvOS-only: wraps an unplayable row in a Button so the focus engine can land on it. +struct MediaFileTVOSUnsupportedButton: View { + let onTap: () -> Void + @ViewBuilder let label: () -> Label + + var body: some View { + Button(action: onTap) { label() } + .buttonStyle(.plain) + } +} #else /// iOS/macOS: per-region gesture used by MediaFileRow's icon and text areas. /// Only attaches a gesture when the action differs from `.playVideo`, letting