mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 05:23:41 +00:00
refactor Search
- have a separate body view for each os - macOS: set navigation title for search - macOS: set min width to 835 for main content - macOS: set main window min height to 600 - macOS: don’t have text behind the cancel button - split SearchTextField into macOS and iOS/tvOS - iOS: move search menu to the right - iOS: unified search field - make min width a constant - add option to disable search suggestions Signed-off-by: Toni Förster <toni.foerster@gmail.com>
This commit is contained in:
parent
b0264aaabe
commit
4663aab3da
@ -11,6 +11,7 @@ final class BrowsingSettingsGroupExporter: SettingsGroupExporter {
|
|||||||
"favorites": Defaults[.favorites].compactMap { jsonFromString(FavoriteItem.bridge.serialize($0)) },
|
"favorites": Defaults[.favorites].compactMap { jsonFromString(FavoriteItem.bridge.serialize($0)) },
|
||||||
"widgetsSettings": Defaults[.widgetsSettings].compactMap { widgetSettingsJSON($0) },
|
"widgetsSettings": Defaults[.widgetsSettings].compactMap { widgetSettingsJSON($0) },
|
||||||
"startupSection": Defaults[.startupSection].rawValue,
|
"startupSection": Defaults[.startupSection].rawValue,
|
||||||
|
"showSearchSuggestions": Defaults[.showSearchSuggestions],
|
||||||
"visibleSections": Defaults[.visibleSections].compactMap { $0.rawValue },
|
"visibleSections": Defaults[.visibleSections].compactMap { $0.rawValue },
|
||||||
"showOpenActionsToolbarItem": Defaults[.showOpenActionsToolbarItem],
|
"showOpenActionsToolbarItem": Defaults[.showOpenActionsToolbarItem],
|
||||||
"accountPickerDisplaysAnonymousAccounts": Defaults[.accountPickerDisplaysAnonymousAccounts],
|
"accountPickerDisplaysAnonymousAccounts": Defaults[.accountPickerDisplaysAnonymousAccounts],
|
||||||
|
@ -46,6 +46,10 @@ struct BrowsingSettingsGroupImporter {
|
|||||||
Defaults[.startupSection] = startupSection
|
Defaults[.startupSection] = startupSection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let showSearchSuggestions = json["showSearchSuggestions"].bool {
|
||||||
|
Defaults[.showSearchSuggestions] = showSearchSuggestions
|
||||||
|
}
|
||||||
|
|
||||||
if let visibleSections = json["visibleSections"].array {
|
if let visibleSections = json["visibleSections"].array {
|
||||||
let sections = visibleSections.compactMap { visibleSectionJSON in
|
let sections = visibleSections.compactMap { visibleSectionJSON in
|
||||||
if let visibleSectionString = visibleSectionJSON.rawString(options: []),
|
if let visibleSectionString = visibleSectionJSON.rawString(options: []),
|
||||||
|
@ -18,6 +18,8 @@ final class SearchModel: ObservableObject {
|
|||||||
|
|
||||||
@Published var focused = false
|
@Published var focused = false
|
||||||
|
|
||||||
|
@Default(.showSearchSuggestions) private var showSearchSuggestions
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
var textField: UITextField!
|
var textField: UITextField!
|
||||||
#elseif os(macOS)
|
#elseif os(macOS)
|
||||||
@ -102,7 +104,7 @@ final class SearchModel: ObservableObject {
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
func loadSuggestions(_ query: String) {
|
func loadSuggestions(_ query: String) {
|
||||||
guard accounts.app.supportsSearchSuggestions else {
|
guard accounts.app.supportsSearchSuggestions, showSearchSuggestions else {
|
||||||
querySuggestions.removeAll()
|
querySuggestions.removeAll()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "1.000",
|
||||||
|
"green" : "1.000",
|
||||||
|
"red" : "1.000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.110",
|
||||||
|
"green" : "0.110",
|
||||||
|
"red" : "0.118"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
@ -63,6 +63,14 @@ enum Constants {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static var detailsVisibility: Bool {
|
||||||
|
#if os(iOS)
|
||||||
|
false
|
||||||
|
#else
|
||||||
|
true
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
static var progressViewScale: Double {
|
static var progressViewScale: Double {
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
0.4
|
0.4
|
||||||
@ -95,11 +103,11 @@ enum Constants {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static var detailsVisibility: Bool {
|
static var contentViewMinWidth: Double {
|
||||||
#if os(iOS)
|
#if os(macOS)
|
||||||
false
|
835
|
||||||
#else
|
#else
|
||||||
true
|
0
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ extension Defaults.Keys {
|
|||||||
static let favorites = Key<[FavoriteItem]>("favorites", default: [])
|
static let favorites = Key<[FavoriteItem]>("favorites", default: [])
|
||||||
static let widgetsSettings = Key<[WidgetSettings]>("widgetsSettings", default: [])
|
static let widgetsSettings = Key<[WidgetSettings]>("widgetsSettings", default: [])
|
||||||
static let startupSection = Key<StartupSection>("startupSection", default: .home)
|
static let startupSection = Key<StartupSection>("startupSection", default: .home)
|
||||||
|
static let showSearchSuggestions = Key<Bool>("showSearchSuggestions", default: true)
|
||||||
static let visibleSections = Key<Set<VisibleSection>>("visibleSections", default: [.subscriptions, .trending, .playlists])
|
static let visibleSections = Key<Set<VisibleSection>>("visibleSections", default: [.subscriptions, .trending, .playlists])
|
||||||
|
|
||||||
static let showOpenActionsToolbarItem = Key<Bool>("showOpenActionsToolbarItem", default: false)
|
static let showOpenActionsToolbarItem = Key<Bool>("showOpenActionsToolbarItem", default: false)
|
||||||
|
@ -152,7 +152,7 @@ struct HomeView: View {
|
|||||||
#endif
|
#endif
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.background(Color.secondaryBackground)
|
.background(Color.secondaryBackground)
|
||||||
.frame(minWidth: 360)
|
.frame(minWidth: Constants.contentViewMinWidth)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItemGroup(placement: .automatic) {
|
ToolbarItemGroup(placement: .automatic) {
|
||||||
HideWatchedButtons()
|
HideWatchedButtons()
|
||||||
|
@ -169,7 +169,7 @@ struct ContentView: View {
|
|||||||
.statusBarHidden(player.playingFullScreen)
|
.statusBarHidden(player.playingFullScreen)
|
||||||
#endif
|
#endif
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.frame(minWidth: 1200)
|
.frame(minWidth: 1200, minHeight: 600)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,64 +1,95 @@
|
|||||||
import Repeat
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct SearchTextField: View {
|
struct SearchTextField: View {
|
||||||
private var navigation = NavigationModel.shared
|
private var navigation = NavigationModel.shared
|
||||||
@ObservedObject private var state = SearchModel.shared
|
@ObservedObject private var state = SearchModel.shared
|
||||||
|
|
||||||
var body: some View {
|
#if os(macOS)
|
||||||
ZStack {
|
var body: some View {
|
||||||
#if os(macOS)
|
ZStack {
|
||||||
fieldBorder
|
fieldBorder
|
||||||
#endif
|
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
#if os(macOS)
|
|
||||||
Image(systemName: "magnifyingglass")
|
Image(systemName: "magnifyingglass")
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFill()
|
.scaledToFill()
|
||||||
.frame(width: 12, height: 12)
|
.frame(width: 12, height: 12)
|
||||||
.padding(.horizontal, 8)
|
.padding(.horizontal, 6)
|
||||||
.opacity(0.8)
|
.opacity(0.8)
|
||||||
#endif
|
|
||||||
TextField("Search...", text: $state.queryText) {
|
|
||||||
state.changeQuery { query in
|
|
||||||
query.query = state.queryText
|
|
||||||
navigation.hideKeyboard()
|
|
||||||
}
|
|
||||||
RecentsModel.shared.addQuery(state.queryText)
|
|
||||||
}
|
|
||||||
.disableAutocorrection(true)
|
|
||||||
#if os(macOS)
|
|
||||||
.frame(maxWidth: 190)
|
|
||||||
.textFieldStyle(.plain)
|
|
||||||
#else
|
|
||||||
.frame(minWidth: 200)
|
|
||||||
.textFieldStyle(.roundedBorder)
|
|
||||||
.padding(.horizontal, 5)
|
|
||||||
.padding(.trailing, state.queryText.isEmpty ? 0 : 10)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if !state.queryText.isEmpty {
|
GeometryReader { geometry in
|
||||||
clearButton
|
TextField("Search...", text: $state.queryText) {
|
||||||
} else {
|
state.changeQuery { query in
|
||||||
#if os(macOS)
|
query.query = state.queryText
|
||||||
|
navigation.hideKeyboard()
|
||||||
|
}
|
||||||
|
RecentsModel.shared.addQuery(state.queryText)
|
||||||
|
}
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.frame(maxWidth: geometry.size.width - 5)
|
||||||
|
.textFieldStyle(.plain)
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.frame(height: 27, alignment: .center)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !state.queryText.isEmpty {
|
||||||
|
clearButton
|
||||||
|
} else {
|
||||||
clearButton
|
clearButton
|
||||||
.opacity(0)
|
.opacity(0)
|
||||||
#endif
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.transaction { t in t.animation = nil }
|
||||||
}
|
}
|
||||||
.transaction { t in t.animation = nil }
|
#else
|
||||||
}
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
HStack {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Image(systemName: "magnifyingglass")
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.padding(.leading, 5)
|
||||||
|
.padding(.trailing, 5)
|
||||||
|
.imageScale(.medium)
|
||||||
|
|
||||||
|
TextField("Search...", text: $state.queryText) {
|
||||||
|
state.changeQuery { query in
|
||||||
|
query.query = state.queryText
|
||||||
|
navigation.hideKeyboard()
|
||||||
|
}
|
||||||
|
RecentsModel.shared.addQuery(state.queryText)
|
||||||
|
}
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.textFieldStyle(.plain)
|
||||||
|
.padding(.vertical, 7)
|
||||||
|
|
||||||
|
if !state.queryText.isEmpty {
|
||||||
|
clearButton
|
||||||
|
.padding(.leading, 5)
|
||||||
|
.padding(.trailing, 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 8)
|
||||||
|
.fill(Color("SearchTextFieldBackground"))
|
||||||
|
)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 0)
|
||||||
|
}
|
||||||
|
.transaction { t in t.animation = nil }
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private var fieldBorder: some View {
|
private var fieldBorder: some View {
|
||||||
RoundedRectangle(cornerRadius: 5, style: .continuous)
|
RoundedRectangle(cornerRadius: 5, style: .continuous)
|
||||||
.fill(Color.background)
|
.fill(Color.background)
|
||||||
.frame(width: 250, height: 32)
|
.frame(width: 250, height: 27)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 5, style: .continuous)
|
RoundedRectangle(cornerRadius: 5, style: .continuous)
|
||||||
.stroke(Color.gray.opacity(0.4), lineWidth: 1)
|
.stroke(Color.gray.opacity(0.4), lineWidth: 1)
|
||||||
.frame(width: 250, height: 31)
|
.frame(width: 250, height: 27)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,15 +98,14 @@ struct SearchTextField: View {
|
|||||||
self.state.queryText = ""
|
self.state.queryText = ""
|
||||||
}) {
|
}) {
|
||||||
Image(systemName: "xmark.circle.fill")
|
Image(systemName: "xmark.circle.fill")
|
||||||
#if os(macOS)
|
|
||||||
.imageScale(.small)
|
|
||||||
#else
|
|
||||||
.imageScale(.medium)
|
.imageScale(.medium)
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.padding(.trailing, 10)
|
.padding(.trailing, 5)
|
||||||
|
#elseif os(iOS)
|
||||||
|
.padding(.trailing, 5)
|
||||||
|
.foregroundColor(.gray)
|
||||||
#endif
|
#endif
|
||||||
.opacity(0.7)
|
.opacity(0.7)
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ struct SearchView: View {
|
|||||||
@Default(.saveRecents) private var saveRecents
|
@Default(.saveRecents) private var saveRecents
|
||||||
@Default(.showHome) private var showHome
|
@Default(.showHome) private var showHome
|
||||||
@Default(.searchListingStyle) private var searchListingStyle
|
@Default(.searchListingStyle) private var searchListingStyle
|
||||||
|
@Default(.showSearchSuggestions) private var showSearchSuggestions
|
||||||
|
|
||||||
private var videos = [Video]()
|
private var videos = [Video]()
|
||||||
|
|
||||||
@ -38,9 +39,9 @@ struct SearchView: View {
|
|||||||
self.videos = videos
|
self.videos = videos
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
#if os(iOS)
|
||||||
VStack {
|
var body: some View {
|
||||||
#if os(iOS)
|
VStack {
|
||||||
VStack {
|
VStack {
|
||||||
if accounts.app.supportsSearchSuggestions, state.query.query != state.queryText {
|
if accounts.app.supportsSearchSuggestions, state.query.query != state.queryText {
|
||||||
SearchSuggestions()
|
SearchSuggestions()
|
||||||
@ -51,27 +52,155 @@ struct SearchView: View {
|
|||||||
}
|
}
|
||||||
.backport
|
.backport
|
||||||
.scrollDismissesKeyboardInteractively()
|
.scrollDismissesKeyboardInteractively()
|
||||||
#else
|
}
|
||||||
|
.environment(\.listingStyle, searchListingStyle)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .principal) {
|
||||||
|
if #available(iOS 15, *) {
|
||||||
|
FocusableSearchTextField()
|
||||||
|
} else {
|
||||||
|
SearchTextField()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
|
searchMenu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.ignoresSafeArea(.keyboard, edges: .bottom)
|
||||||
|
.navigationTitle("Search")
|
||||||
|
.onAppear {
|
||||||
|
if let query {
|
||||||
|
state.queryText = query.query
|
||||||
|
state.resetQuery(query)
|
||||||
|
updateFavoriteItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !videos.isEmpty {
|
||||||
|
state.store.replace(ContentItem.array(of: videos))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: accounts.current) { _ in
|
||||||
|
state.reloadQuery()
|
||||||
|
}
|
||||||
|
.onChange(of: state.queryText) { newQuery in
|
||||||
|
if newQuery.isEmpty {
|
||||||
|
favoriteItem = nil
|
||||||
|
state.resetQuery()
|
||||||
|
} else {
|
||||||
|
updateFavoriteItem()
|
||||||
|
}
|
||||||
|
state.loadSuggestions(newQuery)
|
||||||
|
}
|
||||||
|
.onChange(of: searchSortOrder) { order in
|
||||||
|
state.changeQuery { query in
|
||||||
|
query.sortBy = order
|
||||||
|
updateFavoriteItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: searchDate) { date in
|
||||||
|
state.changeQuery { query in
|
||||||
|
query.date = date
|
||||||
|
updateFavoriteItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: searchDuration) { duration in
|
||||||
|
state.changeQuery { query in
|
||||||
|
query.duration = duration
|
||||||
|
updateFavoriteItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#elseif os(tvOS)
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
ZStack {
|
ZStack {
|
||||||
results
|
results
|
||||||
|
|
||||||
#if os(macOS)
|
|
||||||
if accounts.app.supportsSearchSuggestions, state.query.query != state.queryText {
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
SearchSuggestions()
|
|
||||||
.borderLeading(width: 1, color: Color("ControlsBorderColor"))
|
|
||||||
.frame(maxWidth: 280)
|
|
||||||
.opacity(state.queryText.isEmpty ? 0 : 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#endif
|
}
|
||||||
|
.environment(\.listingStyle, searchListingStyle)
|
||||||
|
.onAppear {
|
||||||
|
if let query {
|
||||||
|
state.queryText = query.query
|
||||||
|
state.resetQuery(query)
|
||||||
|
updateFavoriteItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !videos.isEmpty {
|
||||||
|
state.store.replace(ContentItem.array(of: videos))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: accounts.current) { _ in
|
||||||
|
state.reloadQuery()
|
||||||
|
}
|
||||||
|
.onChange(of: state.queryText) { newQuery in
|
||||||
|
if newQuery.isEmpty {
|
||||||
|
favoriteItem = nil
|
||||||
|
state.resetQuery()
|
||||||
|
} else {
|
||||||
|
updateFavoriteItem()
|
||||||
|
}
|
||||||
|
if showSearchSuggestions {
|
||||||
|
state.loadSuggestions(newQuery)
|
||||||
|
}
|
||||||
|
searchDebounce.invalidate()
|
||||||
|
recentsDebounce.invalidate()
|
||||||
|
|
||||||
|
searchDebounce.debouncing(2) {
|
||||||
|
state.changeQuery { query in
|
||||||
|
query.query = newQuery
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recentsDebounce.debouncing(10) {
|
||||||
|
recents.addQuery(newQuery)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: searchSortOrder) { order in
|
||||||
|
state.changeQuery { query in
|
||||||
|
query.sortBy = order
|
||||||
|
updateFavoriteItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: searchDate) { date in
|
||||||
|
state.changeQuery { query in
|
||||||
|
query.date = date
|
||||||
|
updateFavoriteItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: searchDuration) { duration in
|
||||||
|
state.changeQuery { query in
|
||||||
|
query.duration = duration
|
||||||
|
updateFavoriteItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.searchable(text: $state.queryText) {
|
||||||
|
if !state.queryText.isEmpty {
|
||||||
|
ForEach(state.querySuggestions, id: \.self) { suggestion in
|
||||||
|
Text(suggestion)
|
||||||
|
.searchCompletion(suggestion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.environment(\.listingStyle, searchListingStyle)
|
|
||||||
.toolbar {
|
#elseif os(macOS)
|
||||||
#if os(macOS)
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
results
|
||||||
|
if accounts.app.supportsSearchSuggestions, state.query.query != state.queryText, showSearchSuggestions {
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
SearchSuggestions()
|
||||||
|
.borderLeading(width: 1, color: Color("ControlsBorderColor"))
|
||||||
|
.frame(maxWidth: 262)
|
||||||
|
.opacity(state.queryText.isEmpty ? 0 : 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.environment(\.listingStyle, searchListingStyle)
|
||||||
|
.toolbar {
|
||||||
ToolbarItemGroup(placement: toolbarPlacement) {
|
ToolbarItemGroup(placement: toolbarPlacement) {
|
||||||
ListingStyleButtons(listingStyle: $searchListingStyle)
|
ListingStyleButtons(listingStyle: $searchListingStyle)
|
||||||
HideWatchedButtons()
|
HideWatchedButtons()
|
||||||
@ -84,7 +213,6 @@ struct SearchView: View {
|
|||||||
HStack {
|
HStack {
|
||||||
Text("Sort:")
|
Text("Sort:")
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
|
||||||
searchSortOrderPicker
|
searchSortOrderPicker
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,94 +229,52 @@ struct SearchView: View {
|
|||||||
SearchTextField()
|
SearchTextField()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
if let query {
|
|
||||||
state.queryText = query.query
|
|
||||||
state.resetQuery(query)
|
|
||||||
updateFavoriteItem()
|
|
||||||
}
|
}
|
||||||
|
.onAppear {
|
||||||
if !videos.isEmpty {
|
if let query {
|
||||||
state.store.replace(ContentItem.array(of: videos))
|
state.queryText = query.query
|
||||||
}
|
state.resetQuery(query)
|
||||||
}
|
updateFavoriteItem()
|
||||||
.onChange(of: accounts.current) { _ in
|
|
||||||
state.reloadQuery()
|
|
||||||
}
|
|
||||||
.onChange(of: state.queryText) { newQuery in
|
|
||||||
if newQuery.isEmpty {
|
|
||||||
favoriteItem = nil
|
|
||||||
state.resetQuery()
|
|
||||||
} else {
|
|
||||||
updateFavoriteItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
state.loadSuggestions(newQuery)
|
|
||||||
|
|
||||||
#if os(tvOS)
|
|
||||||
searchDebounce.invalidate()
|
|
||||||
recentsDebounce.invalidate()
|
|
||||||
|
|
||||||
searchDebounce.debouncing(2) {
|
|
||||||
state.changeQuery { query in
|
|
||||||
query.query = newQuery
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
recentsDebounce.debouncing(10) {
|
if !videos.isEmpty {
|
||||||
recents.addQuery(newQuery)
|
state.store.replace(ContentItem.array(of: videos))
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
.onChange(of: searchSortOrder) { order in
|
|
||||||
state.changeQuery { query in
|
|
||||||
query.sortBy = order
|
|
||||||
updateFavoriteItem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChange(of: searchDate) { date in
|
|
||||||
state.changeQuery { query in
|
|
||||||
query.date = date
|
|
||||||
updateFavoriteItem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChange(of: searchDuration) { duration in
|
|
||||||
state.changeQuery { query in
|
|
||||||
query.duration = duration
|
|
||||||
updateFavoriteItem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#if os(tvOS)
|
|
||||||
.searchable(text: $state.queryText) {
|
|
||||||
if !state.queryText.isEmpty {
|
|
||||||
ForEach(state.querySuggestions, id: \.self) { suggestion in
|
|
||||||
Text(suggestion)
|
|
||||||
.searchCompletion(suggestion)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.onChange(of: accounts.current) { _ in
|
||||||
#else
|
state.reloadQuery()
|
||||||
.ignoresSafeArea(.keyboard, edges: .bottom)
|
|
||||||
.navigationTitle("Search")
|
|
||||||
#endif
|
|
||||||
#if os(iOS)
|
|
||||||
.toolbar {
|
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
|
||||||
searchMenu
|
|
||||||
}
|
}
|
||||||
ToolbarItem(placement: .principal) {
|
.onChange(of: state.queryText) { newQuery in
|
||||||
if #available(iOS 15, *) {
|
if newQuery.isEmpty {
|
||||||
FocusableSearchTextField()
|
favoriteItem = nil
|
||||||
|
state.resetQuery()
|
||||||
} else {
|
} else {
|
||||||
SearchTextField()
|
updateFavoriteItem()
|
||||||
|
}
|
||||||
|
state.loadSuggestions(newQuery)
|
||||||
|
}
|
||||||
|
.onChange(of: searchSortOrder) { order in
|
||||||
|
state.changeQuery { query in
|
||||||
|
query.sortBy = order
|
||||||
|
updateFavoriteItem()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onChange(of: searchDate) { date in
|
||||||
|
state.changeQuery { query in
|
||||||
|
query.date = date
|
||||||
|
updateFavoriteItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: searchDuration) { duration in
|
||||||
|
state.changeQuery { query in
|
||||||
|
query.duration = duration
|
||||||
|
updateFavoriteItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(minWidth: Constants.contentViewMinWidth)
|
||||||
|
.navigationTitle("Search")
|
||||||
}
|
}
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
#endif
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
var searchMenu: some View {
|
var searchMenu: some View {
|
||||||
@ -230,11 +316,10 @@ struct SearchView: View {
|
|||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "magnifyingglass")
|
|
||||||
Image(systemName: "chevron.down.circle.fill")
|
Image(systemName: "chevron.down.circle.fill")
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
.imageScale(.large)
|
||||||
}
|
}
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
.imageScale(.medium)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -20,6 +20,7 @@ struct BrowsingSettings: View {
|
|||||||
@Default(.showOpenActionsToolbarItem) private var showOpenActionsToolbarItem
|
@Default(.showOpenActionsToolbarItem) private var showOpenActionsToolbarItem
|
||||||
@Default(.visibleSections) private var visibleSections
|
@Default(.visibleSections) private var visibleSections
|
||||||
@Default(.startupSection) private var startupSection
|
@Default(.startupSection) private var startupSection
|
||||||
|
@Default(.showSearchSuggestions) private var showSearchSuggestions
|
||||||
@Default(.playerButtonSingleTapGesture) private var playerButtonSingleTapGesture
|
@Default(.playerButtonSingleTapGesture) private var playerButtonSingleTapGesture
|
||||||
@Default(.playerButtonDoubleTapGesture) private var playerButtonDoubleTapGesture
|
@Default(.playerButtonDoubleTapGesture) private var playerButtonDoubleTapGesture
|
||||||
@Default(.playerButtonShowsControlButtonsWhenMinimized) private var playerButtonShowsControlButtonsWhenMinimized
|
@Default(.playerButtonShowsControlButtonsWhenMinimized) private var playerButtonShowsControlButtonsWhenMinimized
|
||||||
@ -67,6 +68,7 @@ struct BrowsingSettings: View {
|
|||||||
homeSettings
|
homeSettings
|
||||||
if !accounts.isEmpty {
|
if !accounts.isEmpty {
|
||||||
startupSectionPicker
|
startupSectionPicker
|
||||||
|
showSearchSuggestionsToggle
|
||||||
visibleSectionsSettings
|
visibleSectionsSettings
|
||||||
}
|
}
|
||||||
let interface = interfaceSettings
|
let interface = interfaceSettings
|
||||||
@ -246,6 +248,10 @@ struct BrowsingSettings: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var showSearchSuggestionsToggle: some View {
|
||||||
|
Toggle("Show search suggestions", isOn: $showSearchSuggestions)
|
||||||
|
}
|
||||||
|
|
||||||
private func toggleSection(_ section: VisibleSection, value: Bool) {
|
private func toggleSection(_ section: VisibleSection, value: Bool) {
|
||||||
if value {
|
if value {
|
||||||
visibleSections.insert(section)
|
visibleSections.insert(section)
|
||||||
|
@ -38,12 +38,14 @@ struct SubscriptionsView: View {
|
|||||||
}
|
}
|
||||||
.pickerStyle(.segmented)
|
.pickerStyle(.segmented)
|
||||||
.labelStyle(.titleOnly)
|
.labelStyle(.titleOnly)
|
||||||
|
|
||||||
subscriptionsMenu
|
|
||||||
}
|
}
|
||||||
.frame(maxWidth: 500)
|
.frame(maxWidth: 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
|
subscriptionsMenu
|
||||||
|
}
|
||||||
|
|
||||||
ToolbarItem {
|
ToolbarItem {
|
||||||
RequestErrorButton(error: requestError)
|
RequestErrorButton(error: requestError)
|
||||||
}
|
}
|
||||||
@ -88,7 +90,7 @@ struct SubscriptionsView: View {
|
|||||||
SettingsButtons()
|
SettingsButtons()
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
HStack(spacing: 12) {
|
HStack {
|
||||||
Image(systemName: "chevron.down.circle.fill")
|
Image(systemName: "chevron.down.circle.fill")
|
||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.accentColor)
|
||||||
.imageScale(.large)
|
.imageScale(.large)
|
||||||
|
@ -52,7 +52,7 @@ struct VerticalCells<Header: View>: View {
|
|||||||
.edgesIgnoringSafeArea(edgesIgnoringSafeArea)
|
.edgesIgnoringSafeArea(edgesIgnoringSafeArea)
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.background(Color.secondaryBackground)
|
.background(Color.secondaryBackground)
|
||||||
.frame(minWidth: 360)
|
.frame(minWidth: Constants.contentViewMinWidth)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user