Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
LePips committed Mar 5, 2024
1 parent 11bd2c5 commit d81892d
Show file tree
Hide file tree
Showing 19 changed files with 187 additions and 191 deletions.
2 changes: 0 additions & 2 deletions Shared/Components/RowDivider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@

import SwiftUI

// TODO: rename!
struct RowDivider: View {

var body: some View {
Color.secondarySystemFill
.frame(height: 1)
.padding(.horizontal)
}
}
1 change: 1 addition & 0 deletions Shared/Components/SeparatorHStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import SwiftUI

// https://movingparts.io/variadic-views-in-swiftui

// TODO: add customization for spacing, or just have 0 and have separator handle spacing
struct SeparatorHStack: View {

private var content: () -> any View
Expand Down
2 changes: 1 addition & 1 deletion Shared/Components/Wrapped View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import SwiftUI
struct WrappedView<Content: View>: View {

let content: () -> Content

init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}
Expand Down
2 changes: 1 addition & 1 deletion Shared/Extensions/Collection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ extension Collection {
var asArray: [Element] {
Array(self)
}

var isNotEmpty: Bool {
!isEmpty
}
Expand Down
8 changes: 8 additions & 0 deletions Shared/Extensions/HorizontalAlignment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,12 @@ extension HorizontalAlignment {
}

static let VideoPlayerTitleAlignmentGuide = HorizontalAlignment(VideoPlayerTitleAlignment.self)

struct LibraryRowContentAlignment: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
context[HorizontalAlignment.leading]
}
}

static let LeadingLibraryRowContentAlignmentGuide = HorizontalAlignment(LibraryRowContentAlignment.self)
}
4 changes: 2 additions & 2 deletions Shared/Extensions/JellyfinAPI/JellyfinAPIError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
import Foundation

// TODO: remove and have each occurrence replaced with local errors
struct JellyfinAPIError: Error, Equatable {
struct JellyfinAPIError: LocalizedError, Equatable {

private let message: String

init(_ message: String) {
self.message = message
}

var localizedDescription: String {
var errorDescription: String? {
message
}
}
1 change: 1 addition & 0 deletions Shared/Extensions/JellyfinAPI/NameGuidPair.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ extension NameGuidPair: Displayable {
}
}

// TODO: strong type studios and implement as `LibraryParent`
extension NameGuidPair: LibraryParent {

var libraryType: BaseItemKind? {
Expand Down
2 changes: 1 addition & 1 deletion Shared/Extensions/ViewExtensions/ViewExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ extension View {
/// Instead, use a native `if` statement.
@ViewBuilder
@inlinable
func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
func `if`<Content: View>(_ condition: Bool, @ViewBuilder transform: (Self) -> Content) -> some View {
if condition {
transform(self)
} else {
Expand Down
2 changes: 2 additions & 0 deletions Shared/Objects/LibraryParent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@ protocol LibraryParent: Displayable, Identifiable<String?> {
// different views so this can be renamed when they do, or
// this protocol to be removed entirely and replace just with
// a concrete `BaseItemDto`
//
// edit: studios also implement `LibraryParent` - reconsider above comment
var libraryType: BaseItemKind? { get }
}
5 changes: 2 additions & 3 deletions Shared/Objects/LibraryViewType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ import Defaults
import Foundation
import UIKit

// TODO: `compactList`?
// TODO: also allow list to choose between landscape and portrait poster icons...
// TODO: have just `grid/list`, use separate setting for poster type
enum LibraryViewType: String, CaseIterable, Displayable, Defaults.Serializable {

case landscapeGrid
case portraitGrid
case list
case list // TODO: rename `PortraitList`

// TODO: localize
var displayTitle: String {
Expand Down
14 changes: 0 additions & 14 deletions Shared/Objects/Refreshable.swift

This file was deleted.

12 changes: 3 additions & 9 deletions Shared/Objects/TitledLibraryParent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,7 @@ import JellyfinAPI
/// A basic structure conforming to `LibraryParent` that is meant to only define its `displayTitle`
struct TitledLibraryParent: LibraryParent {

var displayTitle: String
var id: String?
var libraryType: BaseItemKind?

init(displayTitle: String) {
self.displayTitle = displayTitle
self.id = nil
self.libraryType = nil
}
let displayTitle: String
let id: String? = nil
let libraryType: BaseItemKind? = nil
}
163 changes: 61 additions & 102 deletions Shared/ViewModels/SearchViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ import Foundation
import JellyfinAPI
import SwiftUI

// TODO: should probably break out each search into their own "sections"
// like MediaViewModel instead of doing all searching here. Currently,
// we search all item types at once and fail the entire search if just
// one request fails.
final class SearchViewModel: ViewModel, Stateful {

// MARK: Action
Expand Down Expand Up @@ -49,8 +45,8 @@ final class SearchViewModel: ViewModel, Stateful {

@Published
var state: State = .initial
private var searchCancellables: Set<AnyCancellable> = []

private var searchTask: AnyCancellable?
private var searchQuery: PassthroughSubject<String, Never> = .init()

let filterViewModel: FilterViewModel
Expand All @@ -66,18 +62,17 @@ final class SearchViewModel: ViewModel, Stateful {
override init() {
self.filterViewModel = .init()
super.init()

searchQuery
.removeDuplicates()
.debounce(for: 0.5, scheduler: RunLoop.main)
.sink { [weak self] query in
guard let self else { return }
self.searchCancellables.removeAll()

self.searchTask?.cancel()
self.search(query: query)
}
.store(in: &cancellables)

// filterViewModel.$currentFilters
// .sink { newFilters in
// guard self.searchTextSubject.value.isNotEmpty else { return }
Expand All @@ -94,76 +89,91 @@ final class SearchViewModel: ViewModel, Stateful {
return .error(error)
case let .search(query):
if query.isEmpty {
searchCancellables.removeAll()
return.initial
searchTask?.cancel()
searchTask = nil
return .initial
} else {
searchQuery.send(query)
return .searching
}
case .getSuggestions:
Task {
let suggestions = try await getSuggestions()

await MainActor.run {
self.suggestions = suggestions
}
}
.asAnyCancellable()
.store(in: &cancellables)

return state
}
}

private func search(query: String) {
print("searching with: \(query)")
Task {

if Bool.random() {

await MainActor.run {
self.send(.error(.init("We had an error :/")))
}

return
}

searchTask = Task {

do {
let items = try await withThrowingTaskGroup(of: (BaseItemKind, [BaseItemDto]).self, returning: [BaseItemKind: [BaseItemDto]].self) { group in

// TODO: complete
let retrievingItemTypes: [BaseItemKind] = [.movie, .series]


try await Task.sleep(nanoseconds: 3_000_000_000)

let items = try await withThrowingTaskGroup(
of: (BaseItemKind, [BaseItemDto]).self,
returning: [BaseItemKind: [BaseItemDto]].self
) { group in

// Base items
let retrievingItemTypes: [BaseItemKind] = [
.boxSet,
.episode,
.movie,
.series,
]

for type in retrievingItemTypes {
group.addTask {
let items = try await self.getItems(query: query, itemType: type)
return (type, items)
}
}


// People
group.addTask {
let items = try await self.getPeople(query: query)
return (BaseItemKind.person, items)
}

var result: [BaseItemKind: [BaseItemDto]] = [:]

while let items = try await group.next() {
result[items.0] = items.1
}

return result
}


guard !Task.isCancelled else { return }

await MainActor.run {
self.collections = items[.boxSet] ?? []
self.episodes = items[.episode] ?? []
self.movies = items[.movie] ?? []
self.people = items[.person] ?? []
self.series = items[.series] ?? []

self.state = .items
}
} catch {

guard !Task.isCancelled else { return }

await MainActor.run {
self.send(.error(.init(error.localizedDescription)))
}
}
}
.asAnyCancellable()
.store(in: &searchCancellables)
}

private func getItems(query: String, itemType: BaseItemKind) async throws -> [BaseItemDto] {
Expand All @@ -184,75 +194,24 @@ final class SearchViewModel: ViewModel, Stateful {
parameters.sortOrder = filters.sortOrder
parameters.tags = filters.tags.map(\.value)
parameters.years = filters.years.map(\.intValue)

let request = Paths.getItemsByUserID(userID: userSession.user.id, parameters: parameters)
let response = try await userSession.client.send(request)

return response.value.items ?? []
}

// private func _search(with query: String, filters: ItemFilterCollection) {
// getItems(for: query, with: filters, type: .movie, keyPath: \.movies)
// getItems(for: query, with: filters, type: .boxSet, keyPath: \.collections)
// getItems(for: query, with: filters, type: .series, keyPath: \.series)
// getItems(for: query, with: filters, type: .episode, keyPath: \.episodes)
// getPeople(for: query, with: filters)
// }

// private func getItems(
// for query: String,
// with filters: ItemFilterCollection,
// type itemType: BaseItemKind,
// keyPath: ReferenceWritableKeyPath<SearchViewModel, [BaseItemDto]>
// ) {
// let genreIDs = filters.genres.compactMap(\.id)
// let sortBy = filters.sortBy.map(\.filterName)
// let sortOrder = filters.sortOrder.map { SortOrder(rawValue: $0.filterName) ?? .ascending }
// let ItemFilterCollection: [ItemFilter] = filters.filters.compactMap { .init(rawValue: $0.filterName) }
//
// Task {
// let parameters = Paths.GetItemsParameters(
// userID: userSession.user.id,
// limit: 20,
// isRecursive: true,
// searchTerm: query,
// sortOrder: sortOrder,
// fields: ItemFields.allCases,
// includeItemTypes: [itemType],
// filters: ItemFilterCollection,
// sortBy: sortBy,
// enableUserData: true,
// genreIDs: genreIDs,
// enableImages: true
// )
// let request = Paths.getItems(parameters: parameters)
// let response = try await userSession.client.send(request)
//
// await MainActor.run {
// self[keyPath: keyPath] = response.value.items ?? []
// }
// }
// }

// private func getPeople(for query: String?, with filters: ItemFilterCollection) {
// guard !filters.hasFilters else {
// self.people = []
// return
// }
//
// Task {
// let parameters = Paths.GetPersonsParameters(
// limit: 20,
// searchTerm: query
// )
// let request = Paths.getPersons(parameters: parameters)
// let response = try await userSession.client.send(request)
//
// await MainActor.run {
// people = response.value.items ?? []
// }
// }
// }
private func getPeople(query: String) async throws -> [BaseItemDto] {

var parameters = Paths.GetPersonsParameters()
parameters.limit = 20
parameters.searchTerm = query

let request = Paths.getPersons(parameters: parameters)
let response = try await userSession.client.send(request)

return response.value.items ?? []
}

private func getSuggestions() async throws -> [BaseItemDto] {

Expand Down
Loading

0 comments on commit d81892d

Please sign in to comment.