From 5e403c7f15a68b5adcfaafef46d7ef84f87239e3 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Mon, 13 Sep 2021 22:41:16 +0200 Subject: [PATCH] Search UI fixes --- Model/InvidiousAPI.swift | 13 +++ Model/NavigationState.swift | 6 -- Model/PlaybackState.swift | 1 + Model/PlayerState.swift | 4 +- Model/SearchState.swift | 23 ++++- Pearvidious.xcodeproj/project.pbxproj | 10 ++- .../Modifiers/UnsubscribeAlertModifier.swift | 1 + Shared/Navigation/AppSidebarNavigation.swift | 90 ++++++++++++++----- Shared/Navigation/AppSidebarPlaylists.swift | 2 +- .../Navigation/AppSidebarRecentlyOpened.swift | 2 +- .../Navigation/AppSidebarSubscriptions.swift | 2 +- Shared/Navigation/AppTabNavigation.swift | 19 ++++ Shared/Player/PlaybackBar.swift | 16 +++- Shared/Videos/VideoView.swift | 13 +-- Shared/Views/ChannelVideosView.swift | 2 +- Shared/Views/LazyView.swift | 13 +++ Shared/Views/SearchView.swift | 9 +- Shared/Views/VideoContextMenuView.swift | 1 + tvOS/TVNavigationView.swift | 10 +++ tvOS/VideoDetailsView.swift | 1 + 20 files changed, 188 insertions(+), 50 deletions(-) create mode 100644 Shared/Views/LazyView.swift diff --git a/Model/InvidiousAPI.swift b/Model/InvidiousAPI.swift index ea5bdb7c..fa6e0b65 100644 --- a/Model/InvidiousAPI.swift +++ b/Model/InvidiousAPI.swift @@ -50,6 +50,14 @@ final class InvidiousAPI: Service { content.json.arrayValue.map(Video.init) } + configureTransformer("/search/suggestions", requestMethods: [.get]) { (content: Entity) -> [String] in + if let suggestions = content.json.dictionaryValue["suggestions"] { + return suggestions.arrayValue.map(String.init) + } + + return [] + } + configureTransformer("/auth/playlists", requestMethods: [.get]) { (content: Entity) -> [Playlist] in content.json.arrayValue.map(Playlist.init) } @@ -148,6 +156,11 @@ final class InvidiousAPI: Service { return resource } + func searchSuggestions(query: String) -> Resource { + resource("/search/suggestions") + .withParam("q", query.lowercased()) + } + private func searchQuery(_ query: String) -> String { var searchQuery = query diff --git a/Model/NavigationState.swift b/Model/NavigationState.swift index c56ba983..68e383c8 100644 --- a/Model/NavigationState.swift +++ b/Model/NavigationState.swift @@ -28,7 +28,6 @@ final class NavigationState: ObservableObject { openChannels.insert(channel) isChannelOpen = true - tabSelection = .channel(channel.id) } func closeChannel(_ channel: Channel) { @@ -43,11 +42,6 @@ final class NavigationState: ObservableObject { } } - func closeAllChannels() { - isChannelOpen = false - openChannels.removeAll() - } - func showOpenChannel(_ id: Channel.ID) -> Bool { if case .channel = tabSelection { return false diff --git a/Model/PlaybackState.swift b/Model/PlaybackState.swift index 0aae38aa..5fc85d0f 100644 --- a/Model/PlaybackState.swift +++ b/Model/PlaybackState.swift @@ -2,6 +2,7 @@ import CoreMedia import Foundation final class PlaybackState: ObservableObject { + @Published var live = false @Published var stream: Stream? @Published var time: CMTime? diff --git a/Model/PlayerState.swift b/Model/PlayerState.swift index b5cbc6c3..69d8858f 100644 --- a/Model/PlayerState.swift +++ b/Model/PlayerState.swift @@ -62,7 +62,9 @@ final class PlayerState: ObservableObject { } fileprivate func playVideo(_ video: Video) { - if video.hlsUrl != nil { + playbackState.live = video.live + + if video.live { playHlsUrl() return } diff --git a/Model/SearchState.swift b/Model/SearchState.swift index 1ac04ab8..8d31d345 100644 --- a/Model/SearchState.swift +++ b/Model/SearchState.swift @@ -3,14 +3,16 @@ import Siesta import SwiftUI final class SearchState: ObservableObject { + @Published var store = Store<[Video]>() @Published var query = SearchQuery() + + @Published var querySuggestions = Store<[String]>() + @Default(.searchQuery) private var queryText private var previousResource: Resource? private var resource: Resource! - @Published var store = Store<[Video]>() - init() { let newQuery = query newQuery.query = queryText @@ -23,6 +25,23 @@ final class SearchState: ObservableObject { resource.isLoading } + func loadQuerySuggestions(_ query: String) { + let resource = InvidiousAPI.shared.searchSuggestions(query: query) + + resource.addObserver(querySuggestions) + resource.loadIfNeeded() + + if let request = resource.loadIfNeeded() { + request.onSuccess { response in + if let suggestions: [String] = response.typedContent() { + self.querySuggestions = Store<[String]>(suggestions) + } + } + } else { + querySuggestions = Store<[String]>(querySuggestions.collection) + } + } + func changeQuery(_ changeHandler: @escaping (SearchQuery) -> Void = { _ in }) { changeHandler(query) diff --git a/Pearvidious.xcodeproj/project.pbxproj b/Pearvidious.xcodeproj/project.pbxproj index b149c4ee..675400bf 100644 --- a/Pearvidious.xcodeproj/project.pbxproj +++ b/Pearvidious.xcodeproj/project.pbxproj @@ -20,6 +20,9 @@ 37141673267A8E10006CA35D /* Country.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37141672267A8E10006CA35D /* Country.swift */; }; 37141674267A8E10006CA35D /* Country.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37141672267A8E10006CA35D /* Country.swift */; }; 37141675267A8E10006CA35D /* Country.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37141672267A8E10006CA35D /* Country.swift */; }; + 37152EEA26EFEB95004FB96D /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37152EE926EFEB95004FB96D /* LazyView.swift */; }; + 37152EEB26EFEB95004FB96D /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37152EE926EFEB95004FB96D /* LazyView.swift */; }; + 37152EEC26EFEB95004FB96D /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37152EE926EFEB95004FB96D /* LazyView.swift */; }; 371F2F1A269B43D300E4A7AB /* NavigationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371F2F19269B43D300E4A7AB /* NavigationState.swift */; }; 371F2F1B269B43D300E4A7AB /* NavigationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371F2F19269B43D300E4A7AB /* NavigationState.swift */; }; 371F2F1C269B43D300E4A7AB /* NavigationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371F2F19269B43D300E4A7AB /* NavigationState.swift */; }; @@ -191,7 +194,6 @@ 37D4B15F267164AF00C925CA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 37D4B15E267164AF00C925CA /* Assets.xcassets */; }; 37D4B176267164B000C925CA /* PearvidiousUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B175267164B000C925CA /* PearvidiousUITests.swift */; }; 37D4B1802671650A00C925CA /* PearvidiousApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C22671614700C925CA /* PearvidiousApp.swift */; }; - 37D4B1812671653A00C925CA /* AppTabNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C32671614700C925CA /* AppTabNavigation.swift */; }; 37D4B1862671691600C925CA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 37D4B0C42671614800C925CA /* Assets.xcassets */; }; 37D4B18E26717B3800C925CA /* VideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B18B26717B3800C925CA /* VideoView.swift */; }; 37D4B19726717E1500C925CA /* Video.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B19626717E1500C925CA /* Video.swift */; }; @@ -253,6 +255,7 @@ 3711403E26B206A6005B3555 /* SearchState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchState.swift; sourceTree = ""; }; 3714166E267A8ACC006CA35D /* TrendingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingView.swift; sourceTree = ""; }; 37141672267A8E10006CA35D /* Country.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Country.swift; sourceTree = ""; }; + 37152EE926EFEB95004FB96D /* LazyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = ""; }; 371F2F19269B43D300E4A7AB /* NavigationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationState.swift; sourceTree = ""; }; 372915E52687E3B900F5A35B /* Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Defaults.swift; sourceTree = ""; }; 373CFABD26966115003CB2C6 /* CoverSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverSectionView.swift; sourceTree = ""; }; @@ -467,6 +470,7 @@ isa = PBXGroup; children = ( 37BA793E26DB8F97002A0235 /* ChannelVideosView.swift */, + 37152EE926EFEB95004FB96D /* LazyView.swift */, 37BA793A26DB8EE4002A0235 /* PlaylistVideosView.swift */, 37AAF27D26737323007FC770 /* PopularView.swift */, 37AAF27F26737550007FC770 /* SearchView.swift */, @@ -964,6 +968,7 @@ 3763495126DFF59D00B9A393 /* AppSidebarRecentlyOpened.swift in Sources */, 37BA793B26DB8EE4002A0235 /* PlaylistVideosView.swift in Sources */, 37BD07B52698AA4D003EBB87 /* ContentView.swift in Sources */, + 37152EEA26EFEB95004FB96D /* LazyView.swift in Sources */, 37C7A1DA267CACF50010EAD6 /* TrendingCountry.swift in Sources */, 37977583268922F600DD52A8 /* InvidiousAPI.swift in Sources */, 37BE0BD626A1D4A90092E2DB /* PlayerViewController.swift in Sources */, @@ -1043,6 +1048,7 @@ 37EAD870267B9ED100D9E01B /* Segment.swift in Sources */, 37BA793C26DB8EE4002A0235 /* PlaylistVideosView.swift in Sources */, 37141670267A8ACC006CA35D /* TrendingView.swift in Sources */, + 37152EEB26EFEB95004FB96D /* LazyView.swift in Sources */, 377FC7E2267A084A00A6BBAF /* VideoView.swift in Sources */, 37B81B0626D2CEDA00675966 /* PlaybackState.swift in Sources */, 3765788A2685471400D4EA09 /* Playlist.swift in Sources */, @@ -1161,6 +1167,7 @@ 3705B180267B4DFB00704544 /* TrendingCountry.swift in Sources */, 373CFACD26966264003CB2C6 /* SearchQuery.swift in Sources */, 37141675267A8E10006CA35D /* Country.swift in Sources */, + 37152EEC26EFEB95004FB96D /* LazyView.swift in Sources */, 37F49BA826CB0FCE00304AC0 /* PlaylistFormView.swift in Sources */, 373CFAC42696616C003CB2C6 /* CoverSectionRowView.swift in Sources */, 37D4B19926717E1500C925CA /* Video.swift in Sources */, @@ -1168,7 +1175,6 @@ 37AAF2A226741C97007FC770 /* SubscriptionsView.swift in Sources */, 372915E82687E3B900F5A35B /* Defaults.swift in Sources */, 37BAB54C269B39FD00E75ED1 /* TVNavigationView.swift in Sources */, - 37D4B1812671653A00C925CA /* AppTabNavigation.swift in Sources */, 3797758D2689345500DD52A8 /* Store.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Shared/Modifiers/UnsubscribeAlertModifier.swift b/Shared/Modifiers/UnsubscribeAlertModifier.swift index 21367fd6..6037b2d3 100644 --- a/Shared/Modifiers/UnsubscribeAlertModifier.swift +++ b/Shared/Modifiers/UnsubscribeAlertModifier.swift @@ -12,6 +12,7 @@ struct UnsubscribeAlertModifier: ViewModifier { Button("Unsubscribe", role: .destructive) { subscriptions.unsubscribe(channel.id) { navigationState.openChannel(channel) + navigationState.tabSelection = .channel(channel.id) navigationState.sidebarSectionChanged.toggle() } } diff --git a/Shared/Navigation/AppSidebarNavigation.swift b/Shared/Navigation/AppSidebarNavigation.swift index 9be450e0..93632503 100644 --- a/Shared/Navigation/AppSidebarNavigation.swift +++ b/Shared/Navigation/AppSidebarNavigation.swift @@ -4,12 +4,23 @@ import SwiftUI #endif struct AppSidebarNavigation: View { + enum SidebarGroup: String, Identifiable { + case main + + var id: RawValue { + rawValue + } + } + @EnvironmentObject private var navigationState @EnvironmentObject private var playlists + @EnvironmentObject private var searchState @EnvironmentObject private var subscriptions @State private var didApplyPrimaryViewWorkAround = false + @State private var searchQuery = "" + var selection: Binding { navigationState.tabSelectionOptionalBinding } @@ -41,61 +52,94 @@ struct AppSidebarNavigation: View { Text("Select section") } + .searchable(text: $searchQuery, placement: .sidebar) { + ForEach(searchState.querySuggestions.collection, id: \.self) { suggestion in + Text(suggestion) + .searchCompletion(suggestion) + } + } + .onChange(of: searchQuery) { query in + searchState.loadQuerySuggestions(query) + } + .onSubmit(of: .search) { + searchState.changeQuery { query in + query.query = self.searchQuery + } + + navigationState.tabSelection = .search + } } var sidebar: some View { ScrollViewReader { scrollView in List { - mainNavigationLinks - - Group { - AppSidebarRecentlyOpened(selection: selection) - .id("recentlyOpened") - AppSidebarSubscriptions(selection: selection) - AppSidebarPlaylists(selection: selection) + ForEach(sidebarGroups) { group in + sidebarGroupContent(group) + .id(group) } + .onChange(of: navigationState.sidebarSectionChanged) { _ in scrollScrollViewToItem(scrollView: scrollView, for: navigationState.tabSelection) } } + .background { + NavigationLink(destination: SearchView(), tag: TabSelection.search, selection: selection) { + Color.clear + } + .hidden() + } .listStyle(.sidebar) } - - #if os(macOS) - .toolbar { - Button(action: toggleSidebar) { - Image(systemName: "sidebar.left").help("Toggle Sidebar") + .toolbar { + #if os(macOS) + ToolbarItemGroup { + Button(action: toggleSidebar) { + Image(systemName: "sidebar.left").help("Toggle Sidebar") + } } + #endif + } + } + + var sidebarGroups: [SidebarGroup] { + [.main] + } + + func sidebarGroupContent(_ group: SidebarGroup) -> some View { + switch group { + case .main: + return Group { + mainNavigationLinks + + AppSidebarRecentlyOpened(selection: selection) + .id("recentlyOpened") + AppSidebarSubscriptions(selection: selection) + AppSidebarPlaylists(selection: selection) } - #endif + } } var mainNavigationLinks: some View { - Group { - NavigationLink(destination: SubscriptionsView(), tag: TabSelection.subscriptions, selection: selection) { + Section("Videos") { + NavigationLink(destination: LazyView(SubscriptionsView()), tag: TabSelection.subscriptions, selection: selection) { Label("Subscriptions", systemImage: "star.circle.fill") .accessibility(label: Text("Subscriptions")) } - NavigationLink(destination: PopularView(), tag: TabSelection.popular, selection: selection) { + NavigationLink(destination: LazyView(PopularView()), tag: TabSelection.popular, selection: selection) { Label("Popular", systemImage: "chart.bar") .accessibility(label: Text("Popular")) } - NavigationLink(destination: TrendingView(), tag: TabSelection.trending, selection: selection) { + NavigationLink(destination: LazyView(TrendingView()), tag: TabSelection.trending, selection: selection) { Label("Trending", systemImage: "chart.line.uptrend.xyaxis") .accessibility(label: Text("Trending")) } - NavigationLink(destination: PlaylistsView(), tag: TabSelection.playlists, selection: selection) { + NavigationLink(destination: LazyView(PlaylistsView()), tag: TabSelection.playlists, selection: selection) { Label("Playlists", systemImage: "list.and.film") .accessibility(label: Text("Playlists")) } - - NavigationLink(destination: SearchView(), tag: TabSelection.search, selection: selection) { - Label("Search", systemImage: "magnifyingglass") - .accessibility(label: Text("Search")) - } } } diff --git a/Shared/Navigation/AppSidebarPlaylists.swift b/Shared/Navigation/AppSidebarPlaylists.swift index 99248277..12018689 100644 --- a/Shared/Navigation/AppSidebarPlaylists.swift +++ b/Shared/Navigation/AppSidebarPlaylists.swift @@ -10,7 +10,7 @@ struct AppSidebarPlaylists: View { Section(header: Text("Playlists")) { ForEach(playlists.all) { playlist in NavigationLink(tag: TabSelection.playlist(playlist.id), selection: $selection) { - PlaylistVideosView(playlist) + LazyView(PlaylistVideosView(playlist)) } label: { Label(playlist.title, systemImage: AppSidebarNavigation.symbolSystemImage(playlist.title)) .badge(Text("\(playlist.videos.count)")) diff --git a/Shared/Navigation/AppSidebarRecentlyOpened.swift b/Shared/Navigation/AppSidebarRecentlyOpened.swift index 46772f1d..200a23ec 100644 --- a/Shared/Navigation/AppSidebarRecentlyOpened.swift +++ b/Shared/Navigation/AppSidebarRecentlyOpened.swift @@ -14,7 +14,7 @@ struct AppSidebarRecentlyOpened: View { Section(header: Text("Recently Opened")) { ForEach(recentlyOpened) { channel in NavigationLink(tag: TabSelection.channel(channel.id), selection: $selection) { - ChannelVideosView(channel) + LazyView(ChannelVideosView(channel)) } label: { HStack { Label(channel.name, systemImage: AppSidebarNavigation.symbolSystemImage(channel.name)) diff --git a/Shared/Navigation/AppSidebarSubscriptions.swift b/Shared/Navigation/AppSidebarSubscriptions.swift index 39688659..67924961 100644 --- a/Shared/Navigation/AppSidebarSubscriptions.swift +++ b/Shared/Navigation/AppSidebarSubscriptions.swift @@ -10,7 +10,7 @@ struct AppSidebarSubscriptions: View { Section(header: Text("Subscriptions")) { ForEach(subscriptions.all) { channel in NavigationLink(tag: TabSelection.channel(channel.id), selection: $selection) { - ChannelVideosView(channel) + LazyView(ChannelVideosView(channel)) } label: { Label(channel.name, systemImage: AppSidebarNavigation.symbolSystemImage(channel.name)) } diff --git a/Shared/Navigation/AppTabNavigation.swift b/Shared/Navigation/AppTabNavigation.swift index 07029285..67299e43 100644 --- a/Shared/Navigation/AppTabNavigation.swift +++ b/Shared/Navigation/AppTabNavigation.swift @@ -3,6 +3,9 @@ import SwiftUI struct AppTabNavigation: View { @EnvironmentObject private var navigationState + @EnvironmentObject private var searchState + + @State private var searchQuery = "" var body: some View { TabView(selection: $navigationState.tabSelection) { @@ -44,6 +47,22 @@ struct AppTabNavigation: View { NavigationView { SearchView() + .searchable(text: $searchQuery, placement: .navigationBarDrawer(displayMode: .always)) { + ForEach(searchState.querySuggestions.collection, id: \.self) { suggestion in + Text(suggestion) + .searchCompletion(suggestion) + } + } + .onChange(of: searchQuery) { query in + searchState.loadQuerySuggestions(query) + } + .onSubmit(of: .search) { + searchState.changeQuery { query in + query.query = self.searchQuery + } + + navigationState.tabSelection = .search + } } .tabItem { Label("Search", systemImage: "magnifyingglass") diff --git a/Shared/Player/PlaybackBar.swift b/Shared/Player/PlaybackBar.swift index 104efa98..27339651 100644 --- a/Shared/Player/PlaybackBar.swift +++ b/Shared/Player/PlaybackBar.swift @@ -12,7 +12,7 @@ struct PlaybackBar: View { closeButton .frame(width: 60, alignment: .leading) - Text(playbackFinishAtString) + Text(playbackStatus) .foregroundColor(.gray) .font(.caption2) .frame(minWidth: 60, maxWidth: .infinity) @@ -21,7 +21,11 @@ struct PlaybackBar: View { if playbackState.stream != nil { Text(currentStreamString) } else { - Image(systemName: "bolt.horizontal.fill") + if video.live { + Image(systemName: "dot.radiowaves.left.and.right") + } else { + Image(systemName: "bolt.horizontal.fill") + } } } .foregroundColor(.gray) @@ -37,9 +41,13 @@ struct PlaybackBar: View { playbackState.stream != nil ? "\(playbackState.stream!.resolution.height)p" : "" } - var playbackFinishAtString: String { + var playbackStatus: String { guard playbackState.time != nil else { - return "loading..." + if playbackState.live { + return "LIVE" + } else { + return "loading..." + } } let remainingSeconds = video.length - playbackState.time!.seconds diff --git a/Shared/Videos/VideoView.swift b/Shared/Videos/VideoView.swift index 2fd674f9..fcc74f9d 100644 --- a/Shared/Videos/VideoView.swift +++ b/Shared/Videos/VideoView.swift @@ -119,12 +119,15 @@ struct VideoView: View { #endif .padding(.bottom) - if additionalDetailsAvailable { - additionalDetails - .padding(.bottom, 10) - } else { - Spacer() + Group { + if additionalDetailsAvailable { + additionalDetails + } else { + Spacer() + } } + .frame(minHeight: 30, alignment: .top) + .padding(.bottom, 10) } #if os(tvOS) .padding(.horizontal, 8) diff --git a/Shared/Views/ChannelVideosView.swift b/Shared/Views/ChannelVideosView.swift index 4f83eff5..d3290a52 100644 --- a/Shared/Views/ChannelVideosView.swift +++ b/Shared/Views/ChannelVideosView.swift @@ -90,7 +90,7 @@ struct ChannelVideosView: View { var subscriptionToolbarItemPlacement: ToolbarItemPlacement { #if os(iOS) if horizontalSizeClass == .regular { - return .primaryAction + return .primaryAction // swiftlint:disable:this implicit_return } #endif diff --git a/Shared/Views/LazyView.swift b/Shared/Views/LazyView.swift new file mode 100644 index 00000000..f3e8e533 --- /dev/null +++ b/Shared/Views/LazyView.swift @@ -0,0 +1,13 @@ +import Foundation +import SwiftUI + +struct LazyView: View { + let build: () -> Content + init(_ build: @autoclosure @escaping () -> Content) { + self.build = build + } + + var body: Content { + build() + } +} diff --git a/Shared/Views/SearchView.swift b/Shared/Views/SearchView.swift index 9789773a..4f1ef033 100644 --- a/Shared/Views/SearchView.swift +++ b/Shared/Views/SearchView.swift @@ -26,7 +26,6 @@ struct SearchView: View { Spacer() } } - .searchable(text: $queryText) .onAppear { state.changeQuery { query in query.query = queryText @@ -35,7 +34,7 @@ struct SearchView: View { query.duration = searchDuration } } - .onChange(of: queryText) { queryText in + .onChange(of: state.query.query) { queryText in state.changeQuery { query in query.query = queryText } } .onChange(of: searchSortOrder) { order in @@ -48,10 +47,14 @@ struct SearchView: View { state.changeQuery { query in query.duration = duration } } #if !os(tvOS) - .navigationTitle("Search") + .navigationTitle(navigationTitle) #endif } + var navigationTitle: String { + state.query.query.isEmpty ? "Search" : "Search: \"\(state.query.query)\"" + } + var searchFiltersActive: Bool { searchDate != nil || searchDuration != nil } diff --git a/Shared/Views/VideoContextMenuView.swift b/Shared/Views/VideoContextMenuView.swift index 1d885d6e..6b0f51ba 100644 --- a/Shared/Views/VideoContextMenuView.swift +++ b/Shared/Views/VideoContextMenuView.swift @@ -34,6 +34,7 @@ struct VideoContextMenuView: View { var openChannelButton: some View { Button("\(video.author) Channel") { navigationState.openChannel(video.channel) + navigationState.tabSelection = .channel(video.channel.id) navigationState.sidebarSectionChanged.toggle() } } diff --git a/tvOS/TVNavigationView.swift b/tvOS/TVNavigationView.swift index 23e6081d..e7cc6646 100644 --- a/tvOS/TVNavigationView.swift +++ b/tvOS/TVNavigationView.swift @@ -4,6 +4,7 @@ import SwiftUI struct TVNavigationView: View { @EnvironmentObject private var navigationState @EnvironmentObject private var playbackState + @EnvironmentObject private var searchState @State private var showingOptions = false @@ -28,6 +29,15 @@ struct TVNavigationView: View { .tag(TabSelection.playlists) SearchView() + .searchable(text: $searchState.query.query) { + ForEach(searchState.querySuggestions.collection, id: \.self) { suggestion in + Text(suggestion) + .searchCompletion(suggestion) + } + } + .onChange(of: searchState.query.query) { query in + searchState.loadQuerySuggestions(query) + } .tabItem { Image(systemName: "magnifyingglass") } .tag(TabSelection.search) } diff --git a/tvOS/VideoDetailsView.swift b/tvOS/VideoDetailsView.swift index cd1fbae6..02300cc6 100644 --- a/tvOS/VideoDetailsView.swift +++ b/tvOS/VideoDetailsView.swift @@ -105,6 +105,7 @@ struct VideoDetailsView: View { return Button("Open \(channel.name) channel") { navigationState.openChannel(channel) + navigationState.tabSelection = .channel(channel.id) navigationState.returnToDetails = true dismiss() }