From 025dc73e59c8d366b0e7e30b27b12e50039fccae Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Fri, 17 Apr 2026 05:05:49 +0200 Subject: [PATCH] Add channel video search on tvOS Surface the existing in-channel search feature on tvOS as a final tab after Playlists. Selecting it reveals a search field and results area below the tab row, reusing the same API and result views as iOS/macOS. --- Yattee/Localizable.xcstrings | 32 ++++++--- Yattee/Views/Channel/ChannelView.swift | 96 +++++++++++++++++++++----- 2 files changed, 100 insertions(+), 28 deletions(-) diff --git a/Yattee/Localizable.xcstrings b/Yattee/Localizable.xcstrings index 0d43aca1..eec7d7d5 100644 --- a/Yattee/Localizable.xcstrings +++ b/Yattee/Localizable.xcstrings @@ -8359,6 +8359,16 @@ } } }, + "search.title" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search" + } + } + } + }, "search.type.all" : { "localizations" : { "en" : { @@ -14212,17 +14222,6 @@ } } }, - "sidebar.mainItem.playlists" : { - "comment" : "Sidebar main navigation item for playlists", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Playlists" - } - } - } - }, "sidebar.mainItem.downloads" : { "comment" : "Sidebar main navigation item for downloads", "localizations" : { @@ -14267,6 +14266,17 @@ } } }, + "sidebar.mainItem.playlists" : { + "comment" : "Sidebar main navigation item for playlists", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Playlists" + } + } + } + }, "sidebar.mainItem.remoteControl" : { "comment" : "Sidebar main navigation item for remote control", "localizations" : { diff --git a/Yattee/Views/Channel/ChannelView.swift b/Yattee/Views/Channel/ChannelView.swift index 5096469b..03594bf0 100644 --- a/Yattee/Views/Channel/ChannelView.swift +++ b/Yattee/Views/Channel/ChannelView.swift @@ -84,7 +84,9 @@ struct ChannelView: View { #if os(tvOS) // tvOS-specific state @State private var isDescriptionScrollLocked = false + @State private var tvOSShowSearchTab = false @FocusState private var isSubscribeFocused: Bool + @FocusState private var isTVSearchFieldFocused: Bool #endif // Header configuration @@ -630,13 +632,43 @@ struct ChannelView: View { ForEach([ChannelTab.videos, .shorts, .streams, .playlists]) { tab in tvOSTabButton(for: tab) } + if supportsChannelSearch { + tvOSSearchTabButton + } + } + } + + @ViewBuilder + private var tvOSSearchTabButton: some View { + let isSelected = tvOSShowSearchTab + let action = { + if !tvOSShowSearchTab { + tvOSShowSearchTab = true + isSearchActive = true + } + } + let label = Label(String(localized: "search.title"), systemImage: "magnifyingglass") + .fontWeight(isSelected ? .bold : .regular) + .lineLimit(1) + + if isSelected { + Button(action: action) { label } + .buttonStyle(.borderedProminent) + .tint(accentColor) + } else { + Button(action: action) { label } + .buttonStyle(.bordered) } } @ViewBuilder private func tvOSTabButton(for tab: ChannelTab) -> some View { - let isSelected = selectedTab == tab + let isSelected = !tvOSShowSearchTab && selectedTab == tab let action = { + if tvOSShowSearchTab { + tvOSShowSearchTab = false + isSearchActive = false + } if selectedTab != tab { selectedTab = tab Task { @@ -646,6 +678,7 @@ struct ChannelView: View { } let label = Label(tab.title, systemImage: tab.systemImage) .fontWeight(isSelected ? .bold : .regular) + .lineLimit(1) if isSelected { Button(action: action) { label } @@ -663,28 +696,57 @@ struct ChannelView: View { tvOSTabButtons } - ScrollView { - VStack(alignment: .leading, spacing: 0) { - Group { - switch selectedTab { - case .videos: - videosGrid - case .shorts: - shortsGrid - case .streams: - streamsGrid - case .playlists: - playlistsGrid - case .about: - videosGrid + if tvOSShowSearchTab { + tvOSChannelSearchContent + } else { + ScrollView { + VStack(alignment: .leading, spacing: 0) { + Group { + switch selectedTab { + case .videos: + videosGrid + case .shorts: + shortsGrid + case .streams: + streamsGrid + case .playlists: + playlistsGrid + case .about: + videosGrid + } } + .id(selectedTab) } - .id(selectedTab) + .padding(.vertical, 20) + } + .scrollClipDisabled() + } + } + } + + private var tvOSChannelSearchContent: some View { + VStack(alignment: .leading, spacing: 20) { + HStack(spacing: 24) { + TextField(String(localized: "search.placeholder"), text: $searchText) + .textFieldStyle(.plain) + .focused($isTVSearchFieldFocused) + .onSubmit { + Task { await performSearch() } + } + } + .focusSection() + + ScrollView { + if hasSearched || isSearchLoading { + searchResultsContent + .padding(.vertical, 20) } - .padding(.vertical, 20) } .scrollClipDisabled() } + .onAppear { + isTVSearchFieldFocused = true + } } @ViewBuilder