diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index e121e968..65c3d247 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -57,6 +57,7 @@ 53D5E3DD264B47EE00BADDC8 /* MobileVLCKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */; }; 53D5E3DE264B47EE00BADDC8 /* MobileVLCKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 53DE4BD02670961400739748 /* EpisodeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53987CA72657424A00E7EA70 /* EpisodeItemView.swift */; }; + 53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DE4BD1267098F300739748 /* SearchBarView.swift */; }; 53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DF641D263D9C0600A7CD1A /* LibraryView.swift */; }; 53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E4E648263F725B00F67C6B /* MultiSelectorView.swift */; }; 53EE24E6265060780068F029 /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EE24E5265060780068F029 /* LibrarySearchView.swift */; }; @@ -143,6 +144,7 @@ 53C4404D266C75C70049424C /* HandleAPIRequestCompletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandleAPIRequestCompletion.swift; sourceTree = ""; }; 53D5E3DA264B460200BADDC8 /* Cartfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cartfile; sourceTree = ""; }; 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MobileVLCKit.xcframework; path = Carthage/Build/MobileVLCKit.xcframework; sourceTree = ""; }; + 53DE4BD1267098F300739748 /* SearchBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBarView.swift; sourceTree = ""; }; 53DF641D263D9C0600A7CD1A /* LibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = ""; }; 53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryFilterView.swift; sourceTree = ""; }; 53E4E648263F725B00F67C6B /* MultiSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiSelectorView.swift; sourceTree = ""; }; @@ -275,6 +277,7 @@ 535BAEA4264A151C005FA86D /* VideoPlayer.swift */, 53313B8F265EEA6D00947AA3 /* VideoPlayer.storyboard */, 53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */, + 53DE4BD1267098F300739748 /* SearchBarView.swift */, ); path = JellyfinPlayer; sourceTree = ""; @@ -484,6 +487,7 @@ 5377CC01263B596B003A4E83 /* Model.xcdatamodeld in Sources */, 53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */, 53A089D0264DA9DA00D57806 /* MovieItemView.swift in Sources */, + 53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */, 53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */, 621338B32660A07800A81A2A /* LazyView.swift in Sources */, 535870AD2669D8DD00D05A09 /* Typings.swift in Sources */, diff --git a/JellyfinPlayer/EpisodeItemView.swift b/JellyfinPlayer/EpisodeItemView.swift index ef0b53f1..2a596345 100644 --- a/JellyfinPlayer/EpisodeItemView.swift +++ b/JellyfinPlayer/EpisodeItemView.swift @@ -58,9 +58,9 @@ struct EpisodeItemView: View { } var portraitHeaderView: some View { - LazyImage(source: item.getSeriesBackdropImage(baseURL: globalData.server.baseURI!, maxWidth: 1200)) + LazyImage(source: item.getBackdropImage(baseURL: globalData.server.baseURI!, maxWidth: 1200)) .placeholderAndFailure { - Image(uiImage: UIImage(blurHash: item.getSeriesBackdropImageBlurHash(), + Image(uiImage: UIImage(blurHash: item.getBackdropImageBlurHash(), size: CGSize(width: 32, height: 32))!) .resizable() } @@ -249,9 +249,9 @@ struct EpisodeItemView: View { } else { GeometryReader { geometry in ZStack { - LazyImage(source: item.getSeriesBackdropImage(baseURL: globalData.server.baseURI!, maxWidth: 200)) + LazyImage(source: item.getBackdropImage(baseURL: globalData.server.baseURI!, maxWidth: 200)) .placeholderAndFailure { - Image(uiImage: UIImage(blurHash: item.getSeriesBackdropImageBlurHash(), + Image(uiImage: UIImage(blurHash: item.getBackdropImageBlurHash(), size: CGSize(width: 16, height: 16))!) .resizable() .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets diff --git a/JellyfinPlayer/LibrarySearchView.swift b/JellyfinPlayer/LibrarySearchView.swift index 43500297..deb3c2c2 100644 --- a/JellyfinPlayer/LibrarySearchView.swift +++ b/JellyfinPlayer/LibrarySearchView.swift @@ -16,8 +16,12 @@ struct LibrarySearchView: View { @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() - var usingParentID: String + init(usingParentID: String) { + self.usingParentID = usingParentID + } func onAppear() { recalcTracks() @@ -26,40 +30,39 @@ struct LibrarySearchView: View { func requestSearch(query: String) { isLoading = true - ItemsAPI.getItems(userId: globalData.user.user_id!, searchTerm: query, parentId: usingParentID) + print(usingParentID) + ItemsAPI.getItemsByUserId(userId: globalData.user.user_id!, limit: 60, recursive: true, searchTerm: query, sortOrder: [.ascending], parentId: (usingParentID != "" ? usingParentID : nil), fields: [.parentId,.primaryImageAspectRatio,.basicSyncInfo], includeItemTypes: ["Movie","Series"], sortBy: ["SortName"], enableUserData: true, enableImages: true) .sink(receiveCompletion: { completion in HandleAPIRequestCompletion(globalData: globalData, completion: completion) }, receiveValue: { response in - items = response.items! + items = response.items ?? [] + isLoading = false }) .store(in: &globalData.pendingAPIRequests) - - isLoading = false } //MARK: tracks for grid @State private var tracks: [GridItem] = [] func recalcTracks() { let trkCnt = Int(floor(UIScreen.main.bounds.size.width / 125)) - _tracks.wrappedValue = [] + tracks = [] for _ in 0 ..< trkCnt { - _tracks.wrappedValue.append(GridItem(.flexible())) + tracks.append(GridItem(.flexible())) } } var body: some View { - ZStack { + VStack { + Spacer().frame(height: 6) + SearchBar(text: $searchQuery) if(isLoading == true) { + Spacer() ProgressView() - } - if(!items.isEmpty) { - VStack { - Spacer().frame(height: 6) - TextField("Search", text: $searchQuery) - .padding(.horizontal, 10) - .foregroundColor(Color.secondary) - .textFieldStyle(RoundedBorderTextFieldStyle()) + Spacer() + } else { + if(!items.isEmpty) { ScrollView(.vertical) { + Spacer().frame(height: 16) LazyVGrid(columns: tracks) { ForEach(items, id: \.id) { item in NavigationLink(destination: ItemView(item: item)) { @@ -74,31 +77,36 @@ struct LibrarySearchView: View { } .frame(width: 100, height: 150) .cornerRadius(10) - Text(item.name!) + Text(item.name ?? "") .font(.caption) .fontWeight(.semibold) .foregroundColor(.primary) .lineLimit(1) - Text(String(item.productionYear!)) + Text(String(item.productionYear ?? 0)) .foregroundColor(.secondary) .font(.caption) .fontWeight(.medium) }.frame(width: 100) } } - }.onChange(of: orientationInfo.orientation) { _ in + } + Spacer().frame(height: 16) + .onChange(of: orientationInfo.orientation) { _ in recalcTracks() } } + } else { + Text("No results :(") } - } else { - Text("No results found :(") } } .onAppear(perform: onAppear) .navigationBarTitle("Search", displayMode: .inline) .onChange(of: searchQuery) { query in - requestSearch(query: query) + if(CACurrentMediaTime() - lastSearchTime > 0.5) { + lastSearchTime = CACurrentMediaTime() + requestSearch(query: query) + } } } } diff --git a/JellyfinPlayer/LibraryView.swift b/JellyfinPlayer/LibraryView.swift index 682528bf..84ed2f15 100644 --- a/JellyfinPlayer/LibraryView.swift +++ b/JellyfinPlayer/LibraryView.swift @@ -25,6 +25,7 @@ struct LibraryView: View { @State private var totalPages: Int = 0; @State private var currentPage: Int = 0; + @State private var isSearching: String? = ""; init(usingParentID: String, title: String) { self.usingParentID = usingParentID @@ -175,9 +176,7 @@ struct LibraryView: View { } } if(usingParentID != "") { - NavigationLink(destination: LazyView { - LibrarySearchView(usingParentID: usingParentID) - }) { + NavigationLink(destination: LibrarySearchView(usingParentID: usingParentID)) { Image(systemName: "magnifyingglass") } } diff --git a/JellyfinPlayer/SearchBarView.swift b/JellyfinPlayer/SearchBarView.swift new file mode 100644 index 00000000..20257cb9 --- /dev/null +++ b/JellyfinPlayer/SearchBarView.swift @@ -0,0 +1,42 @@ +/* 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 + * + * Code sourced from AppCoda.com + */ + +import SwiftUI + +struct SearchBar: View { + @Binding var text: String + + @State private var isEditing = false + + var body: some View { + HStack { + + TextField("Search ...", text: $text) + .padding(7) + .padding(.horizontal, 25) + .background(Color(.systemGray6)) + .cornerRadius(8) + .padding(.horizontal, 10) + .onTapGesture { + self.isEditing = true + } + + if isEditing { + Button(action: { + self.isEditing = false + self.text = "" + + }) { + Text("Cancel") + } + .padding(.trailing, 10) + } + } + } +}