mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 21:43:41 +00:00
Add infinite scroll for search (fixes #5)
This commit is contained in:
parent
3326088081
commit
ea6363ba65
@ -91,17 +91,19 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
content.json.arrayValue.map(self.extractVideo)
|
content.json.arrayValue.map(self.extractVideo)
|
||||||
}
|
}
|
||||||
|
|
||||||
configureTransformer(pathPattern("search"), requestMethods: [.get]) { (content: Entity<JSON>) -> [ContentItem] in
|
configureTransformer(pathPattern("search"), requestMethods: [.get]) { (content: Entity<JSON>) -> SearchPage in
|
||||||
content.json.arrayValue.map {
|
let results = content.json.arrayValue.compactMap { json -> ContentItem in
|
||||||
let type = $0.dictionaryValue["type"]?.stringValue
|
let type = json.dictionaryValue["type"]?.stringValue
|
||||||
|
|
||||||
if type == "channel" {
|
if type == "channel" {
|
||||||
return ContentItem(channel: self.extractChannel(from: $0))
|
return ContentItem(channel: self.extractChannel(from: json))
|
||||||
} else if type == "playlist" {
|
} else if type == "playlist" {
|
||||||
return ContentItem(playlist: self.extractChannelPlaylist(from: $0))
|
return ContentItem(playlist: self.extractChannelPlaylist(from: json))
|
||||||
}
|
}
|
||||||
return ContentItem(video: self.extractVideo(from: $0))
|
return ContentItem(video: self.extractVideo(from: json))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return SearchPage(results: results, last: results.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
configureTransformer(pathPattern("search/suggestions"), requestMethods: [.get]) { (content: Entity<JSON>) -> [String] in
|
configureTransformer(pathPattern("search/suggestions"), requestMethods: [.get]) { (content: Entity<JSON>) -> [String] in
|
||||||
@ -238,7 +240,7 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
resource(baseURL: account.url, path: basePathAppending("playlists/\(id)"))
|
resource(baseURL: account.url, path: basePathAppending("playlists/\(id)"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func search(_ query: SearchQuery) -> Resource {
|
func search(_ query: SearchQuery, page: String?) -> Resource {
|
||||||
var resource = resource(baseURL: account.url, path: basePathAppending("search"))
|
var resource = resource(baseURL: account.url, path: basePathAppending("search"))
|
||||||
.withParam("q", searchQuery(query.query))
|
.withParam("q", searchQuery(query.query))
|
||||||
.withParam("sort_by", query.sortBy.parameter)
|
.withParam("sort_by", query.sortBy.parameter)
|
||||||
@ -252,6 +254,10 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
|||||||
resource = resource.withParam("duration", duration.rawValue)
|
resource = resource.withParam("duration", duration.rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let page = page {
|
||||||
|
resource = resource.withParam("page", page)
|
||||||
|
}
|
||||||
|
|
||||||
return resource
|
return resource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,8 +51,13 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
|||||||
self.extractVideos(from: content.json)
|
self.extractVideos(from: content.json)
|
||||||
}
|
}
|
||||||
|
|
||||||
configureTransformer(pathPattern("search")) { (content: Entity<JSON>) -> [ContentItem] in
|
configureTransformer(pathPattern("search")) { (content: Entity<JSON>) -> SearchPage in
|
||||||
self.extractContentItems(from: content.json.dictionaryValue["items"]!)
|
let nextPage = content.json.dictionaryValue["nextpage"]?.stringValue
|
||||||
|
return SearchPage(
|
||||||
|
results: self.extractContentItems(from: content.json.dictionaryValue["items"]!),
|
||||||
|
nextPage: nextPage,
|
||||||
|
last: nextPage == "null"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
configureTransformer(pathPattern("suggestions")) { (content: Entity<JSON>) -> [String] in
|
configureTransformer(pathPattern("suggestions")) { (content: Entity<JSON>) -> [String] in
|
||||||
@ -123,10 +128,18 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
|||||||
.withParam("region", country.rawValue)
|
.withParam("region", country.rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func search(_ query: SearchQuery) -> Resource {
|
func search(_ query: SearchQuery, page: String?) -> Resource {
|
||||||
resource(baseURL: account.instance.apiURL, path: "search")
|
let path = page.isNil ? "search" : "nextpage/search"
|
||||||
|
|
||||||
|
let resource = resource(baseURL: account.instance.apiURL, path: path)
|
||||||
.withParam("q", query.query)
|
.withParam("q", query.query)
|
||||||
.withParam("filter", "")
|
.withParam("filter", "all")
|
||||||
|
|
||||||
|
if page.isNil {
|
||||||
|
return resource
|
||||||
|
}
|
||||||
|
|
||||||
|
return resource.withParam("nextpage", page)
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchSuggestions(query: String) -> Resource {
|
func searchSuggestions(query: String) -> Resource {
|
||||||
|
@ -9,7 +9,7 @@ protocol VideosAPI {
|
|||||||
func channel(_ id: String) -> Resource
|
func channel(_ id: String) -> Resource
|
||||||
func channelVideos(_ id: String) -> Resource
|
func channelVideos(_ id: String) -> Resource
|
||||||
func trending(country: Country, category: TrendingCategory?) -> Resource
|
func trending(country: Country, category: TrendingCategory?) -> Resource
|
||||||
func search(_ query: SearchQuery) -> Resource
|
func search(_ query: SearchQuery, page: String?) -> Resource
|
||||||
func searchSuggestions(query: String) -> Resource
|
func searchSuggestions(query: String) -> Resource
|
||||||
|
|
||||||
func video(_ id: Video.ID) -> Resource
|
func video(_ id: Video.ID) -> Resource
|
||||||
|
@ -42,4 +42,8 @@ enum VideosApp: String, CaseIterable {
|
|||||||
var supportsComments: Bool {
|
var supportsComments: Bool {
|
||||||
self == .piped
|
self == .piped
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var searchUsesIndexedPages: Bool {
|
||||||
|
self == .invidious
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import SwiftUI
|
|||||||
|
|
||||||
final class SearchModel: ObservableObject {
|
final class SearchModel: ObservableObject {
|
||||||
@Published var store = Store<[ContentItem]>()
|
@Published var store = Store<[ContentItem]>()
|
||||||
|
@Published var page: SearchPage?
|
||||||
|
|
||||||
var accounts = AccountsModel()
|
var accounts = AccountsModel()
|
||||||
@Published var query = SearchQuery()
|
@Published var query = SearchQuery()
|
||||||
@ -13,7 +14,6 @@ final class SearchModel: ObservableObject {
|
|||||||
|
|
||||||
@Published var fieldIsFocused = false
|
@Published var fieldIsFocused = false
|
||||||
|
|
||||||
private var previousResource: Resource?
|
|
||||||
private var resource: Resource!
|
private var resource: Resource!
|
||||||
|
|
||||||
var isLoading: Bool {
|
var isLoading: Bool {
|
||||||
@ -23,60 +23,54 @@ final class SearchModel: ObservableObject {
|
|||||||
func changeQuery(_ changeHandler: @escaping (SearchQuery) -> Void = { _ in }) {
|
func changeQuery(_ changeHandler: @escaping (SearchQuery) -> Void = { _ in }) {
|
||||||
changeHandler(query)
|
changeHandler(query)
|
||||||
|
|
||||||
let newResource = accounts.api.search(query)
|
let newResource = accounts.api.search(query, page: nil)
|
||||||
guard newResource != previousResource else {
|
guard newResource != resource else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
previousResource?.removeObservers(ownedBy: store)
|
page = nil
|
||||||
previousResource = newResource
|
|
||||||
|
|
||||||
resource = newResource
|
resource = newResource
|
||||||
resource.addObserver(store)
|
resource.addObserver(store)
|
||||||
|
|
||||||
if !query.isEmpty {
|
if !query.isEmpty {
|
||||||
loadResourceIfNeededAndReplaceStore()
|
loadResource()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func resetQuery(_ query: SearchQuery = SearchQuery()) {
|
func resetQuery(_ query: SearchQuery = SearchQuery()) {
|
||||||
self.query = query
|
self.query = query
|
||||||
|
|
||||||
let newResource = accounts.api.search(query)
|
let newResource = accounts.api.search(query, page: nil)
|
||||||
guard newResource != previousResource else {
|
guard newResource != resource else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
page = nil
|
||||||
store.replace([])
|
store.replace([])
|
||||||
|
|
||||||
previousResource?.removeObservers(ownedBy: store)
|
|
||||||
previousResource = newResource
|
|
||||||
|
|
||||||
resource = newResource
|
resource = newResource
|
||||||
resource.addObserver(store)
|
resource.addObserver(store)
|
||||||
|
|
||||||
if !query.isEmpty {
|
if !query.isEmpty {
|
||||||
loadResourceIfNeededAndReplaceStore()
|
loadResource()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadResourceIfNeededAndReplaceStore() {
|
func loadResource() {
|
||||||
let currentResource = resource!
|
let currentResource = resource!
|
||||||
|
|
||||||
if let request = resource.loadIfNeeded() {
|
resource.load().onSuccess { response in
|
||||||
request.onSuccess { response in
|
if let page: SearchPage = response.typedContent() {
|
||||||
if let results: [ContentItem] = response.typedContent() {
|
self.page = page
|
||||||
self.replace(results, for: currentResource)
|
self.replace(page.results, for: currentResource)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
replace(store.collection, for: currentResource)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func replace(_ videos: [ContentItem], for resource: Resource) {
|
func replace(_ items: [ContentItem], for resource: Resource) {
|
||||||
if self.resource == resource {
|
if self.resource == resource {
|
||||||
store = Store<[ContentItem]>(videos)
|
store = Store<[ContentItem]>(items)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,4 +102,38 @@ final class SearchModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadNextPage() {
|
||||||
|
guard var pageToLoad = page, !pageToLoad.last else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pageToLoad.nextPage.isNil, accounts.app.searchUsesIndexedPages {
|
||||||
|
pageToLoad.nextPage = "2"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource?.removeObservers(ownedBy: store)
|
||||||
|
|
||||||
|
resource = accounts.api.search(query, page: page?.nextPage)
|
||||||
|
resource.addObserver(store)
|
||||||
|
|
||||||
|
resource
|
||||||
|
.load()
|
||||||
|
.onSuccess { response in
|
||||||
|
if let page: SearchPage = response.typedContent() {
|
||||||
|
var nextPage: Int?
|
||||||
|
if self.accounts.app.searchUsesIndexedPages {
|
||||||
|
nextPage = Int(pageToLoad.nextPage ?? "0")
|
||||||
|
}
|
||||||
|
|
||||||
|
self.page = page
|
||||||
|
|
||||||
|
if self.accounts.app.searchUsesIndexedPages {
|
||||||
|
self.page?.nextPage = String((nextPage ?? 1) + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.replace(self.store.collection + page.results, for: self.resource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
7
Model/Search/SearchPage.swift
Normal file
7
Model/Search/SearchPage.swift
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct SearchPage {
|
||||||
|
var results = [ContentItem]()
|
||||||
|
var nextPage: String?
|
||||||
|
var last = false
|
||||||
|
}
|
@ -61,11 +61,8 @@ final class SearchQuery: ObservableObject {
|
|||||||
@Published var date: SearchQuery.Date? = .month
|
@Published var date: SearchQuery.Date? = .month
|
||||||
@Published var duration: SearchQuery.Duration?
|
@Published var duration: SearchQuery.Duration?
|
||||||
|
|
||||||
@Published var page = 1
|
init(query: String = "", sortBy: SearchQuery.SortOrder = .relevance, date: SearchQuery.Date? = nil, duration: SearchQuery.Duration? = nil) {
|
||||||
|
|
||||||
init(query: String = "", page: Int = 1, sortBy: SearchQuery.SortOrder = .relevance, date: SearchQuery.Date? = nil, duration: SearchQuery.Duration? = nil) {
|
|
||||||
self.query = query
|
self.query = query
|
||||||
self.page = page
|
|
||||||
self.sortBy = sortBy
|
self.sortBy = sortBy
|
||||||
self.date = date
|
self.date = date
|
||||||
self.duration = duration
|
self.duration = duration
|
||||||
|
@ -30,8 +30,7 @@ extension Defaults.Keys {
|
|||||||
.init(section: .channel("UC-lHJZR3Gqxm24_Vd_AJ5Yw", "PewDiePie")),
|
.init(section: .channel("UC-lHJZR3Gqxm24_Vd_AJ5Yw", "PewDiePie")),
|
||||||
.init(section: .channel("UCXuqSBlHAE6Xw-yeJA0Tunw", "Linus Tech Tips")),
|
.init(section: .channel("UCXuqSBlHAE6Xw-yeJA0Tunw", "Linus Tech Tips")),
|
||||||
.init(section: .channel("UCBJycsmduvYEL83R_U4JriQ", "Marques Brownlee")),
|
.init(section: .channel("UCBJycsmduvYEL83R_U4JriQ", "Marques Brownlee")),
|
||||||
.init(section: .channel("UCE_M8A5yxnLfW0KghEeajjw", "Apple")),
|
.init(section: .channel("UCE_M8A5yxnLfW0KghEeajjw", "Apple"))
|
||||||
.init(section: .searchQuery("Apple Pie Recipes", "", "", ""))
|
|
||||||
])
|
])
|
||||||
|
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
|
@ -29,6 +29,12 @@ private struct CurrentPlaylistID: EnvironmentKey {
|
|||||||
static let defaultValue: String? = nil
|
static let defaultValue: String? = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct LoadMoreContentHandler: EnvironmentKey {
|
||||||
|
static let defaultValue: LoadMoreContentHandlerClosure = { print("infinite load") }
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias LoadMoreContentHandlerClosure = () -> Void
|
||||||
|
|
||||||
extension EnvironmentValues {
|
extension EnvironmentValues {
|
||||||
var inNavigationView: Bool {
|
var inNavigationView: Bool {
|
||||||
get { self[InNavigationViewKey.self] }
|
get { self[InNavigationViewKey.self] }
|
||||||
@ -59,4 +65,9 @@ extension EnvironmentValues {
|
|||||||
get { self[CurrentPlaylistID.self] }
|
get { self[CurrentPlaylistID.self] }
|
||||||
set { self[CurrentPlaylistID.self] = newValue }
|
set { self[CurrentPlaylistID.self] = newValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var loadMoreContentHandler: LoadMoreContentHandlerClosure {
|
||||||
|
get { self[LoadMoreContentHandler.self] }
|
||||||
|
set { self[LoadMoreContentHandler.self] = newValue }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,12 +113,15 @@ struct FavoriteItemView: View {
|
|||||||
return accounts.api.playlist(id)
|
return accounts.api.playlist(id)
|
||||||
|
|
||||||
case let .searchQuery(text, date, duration, order):
|
case let .searchQuery(text, date, duration, order):
|
||||||
return accounts.api.search(.init(
|
return accounts.api.search(
|
||||||
|
.init(
|
||||||
query: text,
|
query: text,
|
||||||
sortBy: SearchQuery.SortOrder(rawValue: order) ?? .uploadDate,
|
sortBy: SearchQuery.SortOrder(rawValue: order) ?? .uploadDate,
|
||||||
date: SearchQuery.Date(rawValue: date),
|
date: SearchQuery.Date(rawValue: date),
|
||||||
duration: SearchQuery.Duration(rawValue: duration)
|
duration: SearchQuery.Duration(rawValue: duration)
|
||||||
))
|
),
|
||||||
|
page: nil
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -199,10 +199,12 @@ struct SearchView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HorizontalCells(items: items)
|
HorizontalCells(items: items)
|
||||||
|
.environment(\.loadMoreContentHandler) { state.loadNextPage() }
|
||||||
}
|
}
|
||||||
.edgesIgnoringSafeArea(.horizontal)
|
.edgesIgnoringSafeArea(.horizontal)
|
||||||
#else
|
#else
|
||||||
VerticalCells(items: items)
|
VerticalCells(items: items)
|
||||||
|
.environment(\.loadMoreContentHandler) { state.loadNextPage() }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if noResults {
|
if noResults {
|
||||||
|
@ -4,6 +4,8 @@ import SwiftUI
|
|||||||
struct HorizontalCells: View {
|
struct HorizontalCells: View {
|
||||||
var items = [ContentItem]()
|
var items = [ContentItem]()
|
||||||
|
|
||||||
|
@Environment(\.loadMoreContentHandler) private var loadMoreContentHandler
|
||||||
|
|
||||||
@Default(.channelOnThumbnail) private var channelOnThumbnail
|
@Default(.channelOnThumbnail) private var channelOnThumbnail
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -12,6 +14,7 @@ struct HorizontalCells: View {
|
|||||||
ForEach(items) { item in
|
ForEach(items) { item in
|
||||||
ContentItemView(item: item)
|
ContentItemView(item: item)
|
||||||
.environment(\.horizontalCells, true)
|
.environment(\.horizontalCells, true)
|
||||||
|
.onAppear { loadMoreContentItemsIfNeeded(current: item) }
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
.frame(width: 580)
|
.frame(width: 580)
|
||||||
.padding(.trailing, 20)
|
.padding(.trailing, 20)
|
||||||
@ -33,6 +36,13 @@ struct HorizontalCells: View {
|
|||||||
.edgesIgnoringSafeArea(.horizontal)
|
.edgesIgnoringSafeArea(.horizontal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadMoreContentItemsIfNeeded(current item: ContentItem) {
|
||||||
|
let thresholdIndex = items.index(items.endIndex, offsetBy: -5)
|
||||||
|
if items.firstIndex(where: { $0.id == item.id }) == thresholdIndex {
|
||||||
|
loadMoreContentHandler()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var cellHeight: Double {
|
var cellHeight: Double {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
560
|
560
|
||||||
|
@ -6,6 +6,8 @@ struct VerticalCells: View {
|
|||||||
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@Environment(\.loadMoreContentHandler) private var loadMoreContentHandler
|
||||||
|
|
||||||
var items = [ContentItem]()
|
var items = [ContentItem]()
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -13,6 +15,7 @@ struct VerticalCells: View {
|
|||||||
LazyVGrid(columns: columns, alignment: .center) {
|
LazyVGrid(columns: columns, alignment: .center) {
|
||||||
ForEach(items.sorted { $0 < $1 }) { item in
|
ForEach(items.sorted { $0 < $1 }) { item in
|
||||||
ContentItemView(item: item)
|
ContentItemView(item: item)
|
||||||
|
.onAppear { loadMoreContentItemsIfNeeded(current: item) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
@ -24,6 +27,13 @@ struct VerticalCells: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadMoreContentItemsIfNeeded(current item: ContentItem) {
|
||||||
|
let thresholdIndex = items.index(items.endIndex, offsetBy: -5)
|
||||||
|
if items.firstIndex(where: { $0.id == item.id }) == thresholdIndex {
|
||||||
|
loadMoreContentHandler()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var columns: [GridItem] {
|
var columns: [GridItem] {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
items.count < 3 ? Array(repeating: GridItem(.fixed(500)), count: [items.count, 1].max()!) : adaptiveItem
|
items.count < 3 ? Array(repeating: GridItem(.fixed(500)), count: [items.count, 1].max()!) : adaptiveItem
|
||||||
|
@ -174,6 +174,9 @@
|
|||||||
375168D62700FAFF008F96A6 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D52700FAFF008F96A6 /* Debounce.swift */; };
|
375168D62700FAFF008F96A6 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D52700FAFF008F96A6 /* Debounce.swift */; };
|
||||||
375168D72700FDB8008F96A6 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D52700FAFF008F96A6 /* Debounce.swift */; };
|
375168D72700FDB8008F96A6 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D52700FAFF008F96A6 /* Debounce.swift */; };
|
||||||
375168D82700FDB9008F96A6 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D52700FAFF008F96A6 /* Debounce.swift */; };
|
375168D82700FDB9008F96A6 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D52700FAFF008F96A6 /* Debounce.swift */; };
|
||||||
|
3751B4B227836902000B7DF4 /* SearchPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3751B4B127836902000B7DF4 /* SearchPage.swift */; };
|
||||||
|
3751B4B327836902000B7DF4 /* SearchPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3751B4B127836902000B7DF4 /* SearchPage.swift */; };
|
||||||
|
3751B4B427836902000B7DF4 /* SearchPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3751B4B127836902000B7DF4 /* SearchPage.swift */; };
|
||||||
3758638A2721B0A9000CB14E /* ChannelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3743B86727216D3600261544 /* ChannelCell.swift */; };
|
3758638A2721B0A9000CB14E /* ChannelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3743B86727216D3600261544 /* ChannelCell.swift */; };
|
||||||
37599F30272B42810087F250 /* FavoriteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F2F272B42810087F250 /* FavoriteItem.swift */; };
|
37599F30272B42810087F250 /* FavoriteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F2F272B42810087F250 /* FavoriteItem.swift */; };
|
||||||
37599F31272B42810087F250 /* FavoriteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F2F272B42810087F250 /* FavoriteItem.swift */; };
|
37599F31272B42810087F250 /* FavoriteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F2F272B42810087F250 /* FavoriteItem.swift */; };
|
||||||
@ -648,6 +651,7 @@
|
|||||||
374C0542272496E4009BDDBE /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = macOS/AppDelegate.swift; sourceTree = SOURCE_ROOT; };
|
374C0542272496E4009BDDBE /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = macOS/AppDelegate.swift; sourceTree = SOURCE_ROOT; };
|
||||||
374C0544272496FD009BDDBE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
374C0544272496FD009BDDBE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
375168D52700FAFF008F96A6 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = "<group>"; };
|
375168D52700FAFF008F96A6 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = "<group>"; };
|
||||||
|
3751B4B127836902000B7DF4 /* SearchPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchPage.swift; sourceTree = "<group>"; };
|
||||||
37599F2F272B42810087F250 /* FavoriteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItem.swift; sourceTree = "<group>"; };
|
37599F2F272B42810087F250 /* FavoriteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItem.swift; sourceTree = "<group>"; };
|
||||||
37599F33272B44000087F250 /* FavoritesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesModel.swift; sourceTree = "<group>"; };
|
37599F33272B44000087F250 /* FavoritesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesModel.swift; sourceTree = "<group>"; };
|
||||||
37599F37272B4D740087F250 /* FavoriteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteButton.swift; sourceTree = "<group>"; };
|
37599F37272B4D740087F250 /* FavoriteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteButton.swift; sourceTree = "<group>"; };
|
||||||
@ -1341,6 +1345,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
3711403E26B206A6005B3555 /* SearchModel.swift */,
|
3711403E26B206A6005B3555 /* SearchModel.swift */,
|
||||||
|
3751B4B127836902000B7DF4 /* SearchPage.swift */,
|
||||||
373CFACA26966264003CB2C6 /* SearchQuery.swift */,
|
373CFACA26966264003CB2C6 /* SearchQuery.swift */,
|
||||||
);
|
);
|
||||||
path = Search;
|
path = Search;
|
||||||
@ -1868,6 +1873,7 @@
|
|||||||
37599F34272B44000087F250 /* FavoritesModel.swift in Sources */,
|
37599F34272B44000087F250 /* FavoritesModel.swift in Sources */,
|
||||||
37BA794726DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */,
|
37BA794726DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */,
|
||||||
377FC7DC267A081800A6BBAF /* PopularView.swift in Sources */,
|
377FC7DC267A081800A6BBAF /* PopularView.swift in Sources */,
|
||||||
|
3751B4B227836902000B7DF4 /* SearchPage.swift in Sources */,
|
||||||
37CC3F4C270CFE1700608308 /* PlayerQueueView.swift in Sources */,
|
37CC3F4C270CFE1700608308 /* PlayerQueueView.swift in Sources */,
|
||||||
37FFC440272734C3009FFD26 /* Throttle.swift in Sources */,
|
37FFC440272734C3009FFD26 /* Throttle.swift in Sources */,
|
||||||
3705B182267B4E4900704544 /* TrendingCategory.swift in Sources */,
|
3705B182267B4E4900704544 /* TrendingCategory.swift in Sources */,
|
||||||
@ -2035,6 +2041,7 @@
|
|||||||
37169AA32729D98A0011DE61 /* InstancesBridge.swift in Sources */,
|
37169AA32729D98A0011DE61 /* InstancesBridge.swift in Sources */,
|
||||||
37B044B826F7AB9000E1419D /* SettingsView.swift in Sources */,
|
37B044B826F7AB9000E1419D /* SettingsView.swift in Sources */,
|
||||||
3765788A2685471400D4EA09 /* Playlist.swift in Sources */,
|
3765788A2685471400D4EA09 /* Playlist.swift in Sources */,
|
||||||
|
3751B4B327836902000B7DF4 /* SearchPage.swift in Sources */,
|
||||||
3782B9532755667600990149 /* String+Format.swift in Sources */,
|
3782B9532755667600990149 /* String+Format.swift in Sources */,
|
||||||
37E2EEAC270656EC00170416 /* PlayerControlsView.swift in Sources */,
|
37E2EEAC270656EC00170416 /* PlayerControlsView.swift in Sources */,
|
||||||
37BF662027308884008CCFB0 /* DropFavoriteOutside.swift in Sources */,
|
37BF662027308884008CCFB0 /* DropFavoriteOutside.swift in Sources */,
|
||||||
@ -2262,6 +2269,7 @@
|
|||||||
376BE50827347B57009AD608 /* SettingsHeader.swift in Sources */,
|
376BE50827347B57009AD608 /* SettingsHeader.swift in Sources */,
|
||||||
37A9966026D6F9B9006E3224 /* FavoritesView.swift in Sources */,
|
37A9966026D6F9B9006E3224 /* FavoritesView.swift in Sources */,
|
||||||
37001565271B1F250049C794 /* AccountsModel.swift in Sources */,
|
37001565271B1F250049C794 /* AccountsModel.swift in Sources */,
|
||||||
|
3751B4B427836902000B7DF4 /* SearchPage.swift in Sources */,
|
||||||
374C0541272472C0009BDDBE /* PlayerSponsorBlock.swift in Sources */,
|
374C0541272472C0009BDDBE /* PlayerSponsorBlock.swift in Sources */,
|
||||||
37130A61277657300033018A /* PersistenceController.swift in Sources */,
|
37130A61277657300033018A /* PersistenceController.swift in Sources */,
|
||||||
37E70929271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */,
|
37E70929271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */,
|
||||||
|
Loading…
Reference in New Issue
Block a user