From 88ed1c4a3e7bd4c1ab24f383c574d6e8dc0b2690 Mon Sep 17 00:00:00 2001 From: PangMo5 Date: Sat, 19 Jun 2021 04:51:04 +0900 Subject: [PATCH] add LibrarySearchViewModel --- JellyfinPlayer.xcodeproj/project.pbxproj | 8 ++ JellyfinPlayer/LibraryListView.swift | 2 +- JellyfinPlayer/LibrarySearchView.swift | 82 +++++-------------- JellyfinPlayer/LibraryView.swift | 2 +- .../ViewModels/LibrarySearchviewModel.swift | 45 ++++++++++ 5 files changed, 76 insertions(+), 63 deletions(-) create mode 100644 Shared/ViewModels/LibrarySearchviewModel.swift diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 7e3fa144..f7a8da0f 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -118,6 +118,9 @@ 628B953A2670CE250091AF3B /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 628B95392670CE250091AF3B /* KeychainSwift */; }; 628B953C2670D2430091AF3B /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338922660107500A81A2A /* StringExtensions.swift */; }; 62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */; }; + 62E632DC267D2E130063E547 /* LibrarySearchviewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DB267D2E130063E547 /* LibrarySearchviewModel.swift */; }; + 62E632DD267D2E130063E547 /* LibrarySearchviewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DB267D2E130063E547 /* LibrarySearchviewModel.swift */; }; + 62E632DE267D2E170063E547 /* LatestMediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */; }; 62EC3527267665D8000E9F2D /* MobileVLCKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */; }; 62EC3528267665D8000E9F2D /* MobileVLCKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 62EC352C26766675000E9F2D /* ServerEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352B26766675000E9F2D /* ServerEnvironment.swift */; }; @@ -267,6 +270,7 @@ 628B95362670CB800091AF3B /* JellyfinWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinWidget.swift; sourceTree = ""; }; 628B953B2670D1FC0091AF3B /* WidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetExtension.entitlements; sourceTree = ""; }; 62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestMediaViewModel.swift; sourceTree = ""; }; + 62E632DB267D2E130063E547 /* LibrarySearchviewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchviewModel.swift; sourceTree = ""; }; 62EC352B26766675000E9F2D /* ServerEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerEnvironment.swift; sourceTree = ""; }; 62EC352E267666A5000E9F2D /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = ""; }; 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = ""; }; @@ -329,6 +333,7 @@ 625CB57B2678CE1000530A6E /* ViewModel.swift */, 536D3D75267BA9BB0004248C /* MainTabViewModel.swift */, 62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */, + 62E632DB267D2E130063E547 /* LibrarySearchviewModel.swift */, ); path = ViewModels; sourceTree = ""; @@ -695,6 +700,7 @@ 531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */, 53ABFDDE267974E300886593 /* SplashView.swift in Sources */, 53ABFDE8267974EF00886593 /* SplashViewModel.swift in Sources */, + 62E632DE267D2E170063E547 /* LatestMediaViewModel.swift in Sources */, 536D3D88267C17350004248C /* PublicUserButton.swift in Sources */, 536D3D7F267BDF100004248C /* LatestMediaView.swift in Sources */, 531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */, @@ -706,6 +712,7 @@ 535870A52669D8AE00D05A09 /* ParallaxHeader.swift in Sources */, 531690F0267ABF72005D8AB9 /* NextUpView.swift in Sources */, 535870A72669D8AE00D05A09 /* MultiSelectorView.swift in Sources */, + 62E632DD267D2E130063E547 /* LibrarySearchviewModel.swift in Sources */, 536D3D81267BDFC60004248C /* PortraitItemElement.swift in Sources */, 531690E5267ABD5C005D8AB9 /* MainTabView.swift in Sources */, 53ABFDE7267974EF00886593 /* ConnectToServerViewModel.swift in Sources */, @@ -734,6 +741,7 @@ 621338932660107500A81A2A /* StringExtensions.swift in Sources */, 53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */, 625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */, + 62E632DC267D2E130063E547 /* LibrarySearchviewModel.swift in Sources */, 5377CBFE263B596B003A4E83 /* PersistenceController.swift in Sources */, 5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */, 53AD124E26702B8A0094A276 /* SeasonItemView.swift in Sources */, diff --git a/JellyfinPlayer/LibraryListView.swift b/JellyfinPlayer/LibraryListView.swift index f916894a..681a8234 100644 --- a/JellyfinPlayer/LibraryListView.swift +++ b/JellyfinPlayer/LibraryListView.swift @@ -39,7 +39,7 @@ struct LibraryListView: View { .toolbar { ToolbarItemGroup(placement: .navigationBarTrailing) { NavigationLink(destination: LazyView { - LibrarySearchView(usingParentID: "") + LibrarySearchView(viewModel: .init(parentID: nil)) }) { Image(systemName: "magnifyingglass") } diff --git a/JellyfinPlayer/LibrarySearchView.swift b/JellyfinPlayer/LibrarySearchView.swift index 5c28d8f7..c2b8c380 100644 --- a/JellyfinPlayer/LibrarySearchView.swift +++ b/JellyfinPlayer/LibrarySearchView.swift @@ -5,67 +5,33 @@ * Copyright 2021 Aiden Vigue & Jellyfin Contributors */ -import SwiftUI -import JellyfinAPI import Combine +import JellyfinAPI +import SwiftUI struct LibrarySearchView: View { - @StateObject - var tempViewModel = ViewModel() - @State private var items: [BaseItemDto] = [] - @State private var searchQuery: String = "" - @State private var isLoading: Bool = false - private var usingParentID: String = "" - @State private var lastSearchTime: Double = CACurrentMediaTime() - - init(usingParentID: String) { - self.usingParentID = usingParentID - } - - func onAppear() { - recalcTracks() - requestSearch(query: "") - } - - func requestSearch(query: String) { - isLoading = true - DispatchQueue.global(qos: .userInitiated).async { - ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, limit: 60, recursive: true, searchTerm: query, sortOrder: [.ascending], parentId: (usingParentID != "" ? usingParentID : nil), fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], includeItemTypes: ["Movie", "Series"], sortBy: ["SortName"], enableUserData: true, enableImages: true) - .sink(receiveCompletion: { completion in - print(completion) - }, receiveValue: { response in - items = response.items ?? [] - isLoading = false - }) - .store(in: &tempViewModel.cancellables) - } - } + var viewModel: LibrarySearchViewModel // MARK: tracks for grid - @State private var tracks: [GridItem] = [] + + @State + private var tracks: [GridItem] = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125) + func recalcTracks() { - let trkCnt = Int(floor(UIScreen.main.bounds.size.width / 125)) - tracks = [] - for _ in 0 ..< trkCnt { - tracks.append(GridItem(.flexible())) - } + tracks = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125) } var body: some View { VStack { Spacer().frame(height: 6) - SearchBar(text: $searchQuery) - if isLoading == true { - Spacer() - ProgressView() - Spacer() - } else { - if !items.isEmpty { - ScrollView(.vertical) { + SearchBar(text: $viewModel.searchQuery) + ZStack { + ScrollView(.vertical) { + if !viewModel.items.isEmpty { Spacer().frame(height: 16) LazyVGrid(columns: tracks) { - ForEach(items, id: \.id) { item in + ForEach(viewModel.items, id: \.id) { item in NavigationLink(destination: ItemView(item: item)) { VStack(alignment: .leading) { ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash()) @@ -87,26 +53,20 @@ struct LibrarySearchView: View { }.frame(width: 100) } } + Spacer().frame(height: 16) } - Spacer().frame(height: 16) - .onRotate { _ in - recalcTracks() - } + } else { + Text("No results :(") } - } else { - Text("No results :(") + } + if viewModel.isLoading { + ProgressView() } } } - .onAppear(perform: onAppear) .navigationBarTitle("Search", displayMode: .inline) - .onChange(of: searchQuery) { query in - if CACurrentMediaTime() - lastSearchTime > 0.5 { - lastSearchTime = CACurrentMediaTime() - requestSearch(query: query) - } + .onRotate { _ in + recalcTracks() } } } - -// stream NM5 by nicki! diff --git a/JellyfinPlayer/LibraryView.swift b/JellyfinPlayer/LibraryView.swift index de9b71a0..c6c00f9f 100644 --- a/JellyfinPlayer/LibraryView.swift +++ b/JellyfinPlayer/LibraryView.swift @@ -184,7 +184,7 @@ struct LibraryView: View { } } if usingParentID != "" { - NavigationLink(destination: LibrarySearchView(usingParentID: usingParentID)) { + NavigationLink(destination: LibrarySearchView(viewModel: .init(parentID: usingParentID))) { Image(systemName: "magnifyingglass") } } diff --git a/Shared/ViewModels/LibrarySearchviewModel.swift b/Shared/ViewModels/LibrarySearchviewModel.swift new file mode 100644 index 00000000..1f46ee14 --- /dev/null +++ b/Shared/ViewModels/LibrarySearchviewModel.swift @@ -0,0 +1,45 @@ +// +/* + * SwiftFin is subject to the terms of the Mozilla Public + * License, v2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright 2021 Aiden Vigue & Jellyfin Contributors + */ + +import Combine +import Foundation +import JellyfinAPI + +final class LibrarySearchViewModel: ViewModel { + @Published + var items = [BaseItemDto]() + + @Published + var searchQuery = "" + var parentID: String? + + init(parentID: String?) { + self.parentID = parentID + super.init() + + $searchQuery + .debounce(for: 0.25, scheduler: DispatchQueue.main) + .sink(receiveValue: search(with:)) + .store(in: &cancellables) + } + + func search(with query: String) { + ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, limit: 60, recursive: true, searchTerm: query, + sortOrder: [.ascending], parentId: parentID, + fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], + includeItemTypes: ["Movie", "Series"], sortBy: ["SortName"], enableUserData: true, enableImages: true) + .trackActivity(loading) + .sink(receiveCompletion: { [weak self] completion in + self?.HandleAPIRequestCompletion(completion: completion) + }, receiveValue: { [weak self] response in + self?.items = response.items ?? [] + }) + .store(in: &cancellables) + } +}