mirror of
https://github.com/yattee/yattee.git
synced 2024-11-09 15:58:20 +00:00
Adding search queries to favorites
This commit is contained in:
parent
afcefc2e54
commit
ef5ac0ec65
@ -9,6 +9,7 @@ struct FavoriteItem: Codable, Equatable, Identifiable, Defaults.Serializable {
|
|||||||
case channel(String, String)
|
case channel(String, String)
|
||||||
case playlist(String)
|
case playlist(String)
|
||||||
case channelPlaylist(String, String)
|
case channelPlaylist(String, String)
|
||||||
|
case searchQuery(String, String, String, String)
|
||||||
|
|
||||||
var label: String {
|
var label: String {
|
||||||
switch self {
|
switch self {
|
||||||
@ -24,6 +25,19 @@ struct FavoriteItem: Codable, Equatable, Identifiable, Defaults.Serializable {
|
|||||||
return name
|
return name
|
||||||
case let .channelPlaylist(_, name):
|
case let .channelPlaylist(_, name):
|
||||||
return name
|
return name
|
||||||
|
case let .searchQuery(text, date, duration, order):
|
||||||
|
var label = "Search: \"\(text)\""
|
||||||
|
if !date.isEmpty, let date = SearchQuery.Date(rawValue: date), date != .any {
|
||||||
|
label += " from \(date == .today ? date.name : " this \(date.name)")"
|
||||||
|
}
|
||||||
|
if !order.isEmpty, let order = SearchQuery.SortOrder(rawValue: order), order != .relevance {
|
||||||
|
label += " by \(order.name)"
|
||||||
|
}
|
||||||
|
if !duration.isEmpty {
|
||||||
|
label += " (\(duration))"
|
||||||
|
}
|
||||||
|
|
||||||
|
return label
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,10 @@ extension Defaults.Keys {
|
|||||||
static let sponsorBlockCategories = Key<Set<String>>("sponsorBlockCategories", default: Set(SponsorBlockAPI.categories))
|
static let sponsorBlockCategories = Key<Set<String>>("sponsorBlockCategories", default: Set(SponsorBlockAPI.categories))
|
||||||
|
|
||||||
static let favorites = Key<[FavoriteItem]>("favorites", default: [
|
static let favorites = Key<[FavoriteItem]>("favorites", default: [
|
||||||
.init(section: .trending("US", nil))
|
.init(section: .trending("US", nil)),
|
||||||
|
.init(section: .searchQuery("World Discoveries", "", "", "")),
|
||||||
|
.init(section: .searchQuery("Full Body Workout", "", "", "")),
|
||||||
|
.init(section: .searchQuery("Apple Pie Recipes", "", "", ""))
|
||||||
])
|
])
|
||||||
|
|
||||||
static let channelOnThumbnail = Key<Bool>("channelOnThumbnail", default: true)
|
static let channelOnThumbnail = Key<Bool>("channelOnThumbnail", default: true)
|
||||||
|
@ -105,6 +105,14 @@ struct FavoriteItemView: View {
|
|||||||
|
|
||||||
case let .playlist(id):
|
case let .playlist(id):
|
||||||
return accounts.api.playlist(id)
|
return accounts.api.playlist(id)
|
||||||
|
|
||||||
|
case let .searchQuery(text, date, duration, order):
|
||||||
|
return accounts.api.search(.init(
|
||||||
|
query: text,
|
||||||
|
sortBy: SearchQuery.SortOrder(rawValue: order) ?? .uploadDate,
|
||||||
|
date: SearchQuery.Date(rawValue: date),
|
||||||
|
duration: SearchQuery.Duration(rawValue: duration)
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -2,21 +2,19 @@ import Foundation
|
|||||||
import Siesta
|
import Siesta
|
||||||
|
|
||||||
final class FavoriteResourceObserver: ObservableObject, ResourceObserver {
|
final class FavoriteResourceObserver: ObservableObject, ResourceObserver {
|
||||||
@Published var videos = [Video]()
|
@Published var contentItems = [ContentItem]()
|
||||||
|
|
||||||
func resourceChanged(_ resource: Resource, event _: ResourceEvent) {
|
func resourceChanged(_ resource: Resource, event _: ResourceEvent) {
|
||||||
if let videos: [Video] = resource.typedContent() {
|
if let videos: [Video] = resource.typedContent() {
|
||||||
self.videos = videos
|
contentItems = videos.map { ContentItem(video: $0) }
|
||||||
} else if let channel: Channel = resource.typedContent() {
|
} else if let channel: Channel = resource.typedContent() {
|
||||||
videos = channel.videos
|
contentItems = channel.videos.map { ContentItem(video: $0) }
|
||||||
} else if let playlist: ChannelPlaylist = resource.typedContent() {
|
} else if let playlist: ChannelPlaylist = resource.typedContent() {
|
||||||
videos = playlist.videos
|
contentItems = playlist.videos.map { ContentItem(video: $0) }
|
||||||
} else if let playlist: Playlist = resource.typedContent() {
|
} else if let playlist: Playlist = resource.typedContent() {
|
||||||
videos = playlist.videos
|
contentItems = playlist.videos.map { ContentItem(video: $0) }
|
||||||
|
} else if let items: [ContentItem] = resource.typedContent() {
|
||||||
|
contentItems = items
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var contentItems: [ContentItem] {
|
|
||||||
videos.map { ContentItem(video: $0) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ struct SearchView: View {
|
|||||||
@State private var recentsDebounce = Debounce()
|
@State private var recentsDebounce = Debounce()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@State private var favoriteItem: FavoriteItem?
|
||||||
|
|
||||||
@Environment(\.navigationStyle) private var navigationStyle
|
@Environment(\.navigationStyle) private var navigationStyle
|
||||||
|
|
||||||
@EnvironmentObject<AccountsModel> private var accounts
|
@EnvironmentObject<AccountsModel> private var accounts
|
||||||
@ -42,8 +44,17 @@ struct SearchView: View {
|
|||||||
} else {
|
} else {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
if accounts.app.supportsSearchFilters {
|
HStack(spacing: 0) {
|
||||||
filtersHorizontalStack
|
if accounts.app.supportsSearchFilters {
|
||||||
|
filtersHorizontalStack
|
||||||
|
}
|
||||||
|
|
||||||
|
if let favoriteItem = favoriteItem {
|
||||||
|
FavoriteButton(item: favoriteItem)
|
||||||
|
.id(favoriteItem.id)
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
.font(.system(size: 25))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalCells(items: items)
|
HorizontalCells(items: items)
|
||||||
@ -68,6 +79,13 @@ struct SearchView: View {
|
|||||||
.toolbar {
|
.toolbar {
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
ToolbarItemGroup(placement: toolbarPlacement) {
|
ToolbarItemGroup(placement: toolbarPlacement) {
|
||||||
|
#if os(macOS)
|
||||||
|
if let favoriteItem = favoriteItem {
|
||||||
|
FavoriteButton(item: favoriteItem)
|
||||||
|
.id(favoriteItem.id)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if accounts.app.supportsSearchFilters {
|
if accounts.app.supportsSearchFilters {
|
||||||
Section {
|
Section {
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
@ -84,9 +102,20 @@ struct SearchView: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.transaction { t in t.animation = .none }
|
.transaction { t in t.animation = .none }
|
||||||
|
}
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
if let favoriteItem = favoriteItem {
|
||||||
|
FavoriteButton(item: favoriteItem)
|
||||||
|
.id(favoriteItem.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if accounts.app.supportsSearchFilters {
|
||||||
filtersMenu
|
filtersMenu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,6 +125,7 @@ struct SearchView: View {
|
|||||||
if query != nil {
|
if query != nil {
|
||||||
state.queryText = query!.query
|
state.queryText = query!.query
|
||||||
state.resetQuery(query!)
|
state.resetQuery(query!)
|
||||||
|
updateFavoriteItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !videos.isEmpty {
|
if !videos.isEmpty {
|
||||||
@ -120,7 +150,10 @@ struct SearchView: View {
|
|||||||
recentsDebounce.invalidate()
|
recentsDebounce.invalidate()
|
||||||
|
|
||||||
searchDebounce.debouncing(2) {
|
searchDebounce.debouncing(2) {
|
||||||
state.changeQuery { query in query.query = newQuery }
|
state.changeQuery { query in
|
||||||
|
query.query = newQuery
|
||||||
|
updateFavoriteItem()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
recentsDebounce.debouncing(10) {
|
recentsDebounce.debouncing(10) {
|
||||||
@ -131,15 +164,25 @@ struct SearchView: View {
|
|||||||
.onSubmit(of: .search) {
|
.onSubmit(of: .search) {
|
||||||
state.changeQuery { query in query.query = state.queryText }
|
state.changeQuery { query in query.query = state.queryText }
|
||||||
recents.addQuery(state.queryText)
|
recents.addQuery(state.queryText)
|
||||||
|
updateFavoriteItem()
|
||||||
}
|
}
|
||||||
.onChange(of: searchSortOrder) { order in
|
.onChange(of: searchSortOrder) { order in
|
||||||
state.changeQuery { query in query.sortBy = order }
|
state.changeQuery { query in
|
||||||
|
query.sortBy = order
|
||||||
|
updateFavoriteItem()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: searchDate) { date in
|
.onChange(of: searchDate) { date in
|
||||||
state.changeQuery { query in query.date = date }
|
state.changeQuery { query in
|
||||||
|
query.date = date
|
||||||
|
updateFavoriteItem()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: searchDuration) { duration in
|
.onChange(of: searchDuration) { duration in
|
||||||
state.changeQuery { query in query.duration = duration }
|
state.changeQuery { query in
|
||||||
|
query.duration = duration
|
||||||
|
updateFavoriteItem()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
.navigationTitle("Search")
|
.navigationTitle("Search")
|
||||||
@ -192,6 +235,7 @@ struct SearchView: View {
|
|||||||
Button(item.title) {
|
Button(item.title) {
|
||||||
state.queryText = item.title
|
state.queryText = item.title
|
||||||
state.changeQuery { query in query.query = item.title }
|
state.changeQuery { query in query.query = item.title }
|
||||||
|
updateFavoriteItem()
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.swipeActions(edge: .trailing) {
|
.swipeActions(edge: .trailing) {
|
||||||
@ -312,21 +356,21 @@ struct SearchView: View {
|
|||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
searchSortOrderButton
|
searchSortOrderButton
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
.frame(maxWidth: 300, alignment: .trailing)
|
||||||
|
|
||||||
HStack(spacing: 30) {
|
HStack(spacing: 30) {
|
||||||
Text("Duration")
|
Text("Duration")
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
searchDurationButton
|
searchDurationButton
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: 300)
|
||||||
|
|
||||||
HStack(spacing: 30) {
|
HStack(spacing: 30) {
|
||||||
Text("Date")
|
Text("Date")
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
searchDateButton
|
searchDateButton
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: 300, alignment: .leading)
|
||||||
}
|
}
|
||||||
.font(.system(size: 30))
|
.font(.system(size: 30))
|
||||||
}
|
}
|
||||||
@ -348,8 +392,16 @@ struct SearchView: View {
|
|||||||
.foregroundColor(filtersActive ? .accentColor : .secondary)
|
.foregroundColor(filtersActive ? .accentColor : .secondary)
|
||||||
.transaction { t in t.animation = .none }
|
.transaction { t in t.animation = .none }
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
private func updateFavoriteItem() {
|
||||||
|
favoriteItem = FavoriteItem(section: .searchQuery(
|
||||||
|
state.query.query,
|
||||||
|
state.query.date?.rawValue ?? "",
|
||||||
|
state.query.duration?.rawValue ?? "",
|
||||||
|
state.query.sortBy.rawValue
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SearchView_Previews: PreviewProvider {
|
struct SearchView_Previews: PreviewProvider {
|
||||||
|
@ -64,7 +64,7 @@ struct EditFavorites: View {
|
|||||||
.padding(.trailing, 40)
|
.padding(.trailing, 40)
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("Add more Channels and Playlists to your Favorites using button")
|
Text("Add Channels, Playlists and Searches to Favorites using")
|
||||||
Button {} label: {
|
Button {} label: {
|
||||||
Label("Add to Favorites", systemImage: "heart")
|
Label("Add to Favorites", systemImage: "heart")
|
||||||
.labelStyle(.iconOnly)
|
.labelStyle(.iconOnly)
|
||||||
|
Loading…
Reference in New Issue
Block a user