diff --git a/Kiwix.xcodeproj/project.pbxproj b/Kiwix.xcodeproj/project.pbxproj index 04823eba8..33ee89b2d 100644 --- a/Kiwix.xcodeproj/project.pbxproj +++ b/Kiwix.xcodeproj/project.pbxproj @@ -1454,7 +1454,7 @@ CODE_SIGN_ENTITLEMENTS = iOS/Support/Kiwix.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 72; + CURRENT_PROJECT_VERSION = 73; DEVELOPMENT_TEAM = L7HWM3SP3L; ENABLE_BITCODE = YES; GCC_C_LANGUAGE_STANDARD = c11; @@ -1494,7 +1494,7 @@ CODE_SIGN_ENTITLEMENTS = iOS/Support/Kiwix.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 72; + CURRENT_PROJECT_VERSION = 73; DEVELOPMENT_TEAM = L7HWM3SP3L; ENABLE_BITCODE = YES; GCC_C_LANGUAGE_STANDARD = c11; @@ -1586,7 +1586,7 @@ CODE_SIGN_ENTITLEMENTS = iOS/BookmarksWidget/Bookmarks.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 72; + CURRENT_PROJECT_VERSION = 73; DEVELOPMENT_TEAM = L7HWM3SP3L; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = iOS/BookmarksWidget/Info.plist; @@ -1618,7 +1618,7 @@ CODE_SIGN_ENTITLEMENTS = iOS/BookmarksWidget/Bookmarks.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 72; + CURRENT_PROJECT_VERSION = 73; DEVELOPMENT_TEAM = L7HWM3SP3L; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = iOS/BookmarksWidget/Info.plist; diff --git a/Model/Operations/OPDSRefreshOperation/OPDSRefreshOperation.swift b/Model/Operations/OPDSRefreshOperation/OPDSRefreshOperation.swift index 86732329a..d173c7d2f 100644 --- a/Model/Operations/OPDSRefreshOperation/OPDSRefreshOperation.swift +++ b/Model/Operations/OPDSRefreshOperation/OPDSRefreshOperation.swift @@ -22,6 +22,14 @@ class OPDSRefreshOperation: Operation { override func main() { do { os_log("Refresh started.", log: Log.OPDS, type: .debug) + + // if library has never been refreshed before, apply initial language filter + if Defaults[.libraryLastRefresh] == nil { + DispatchQueue.main.sync { + guard let languageCode = Locale.current.languageCode else { return } + Defaults[.libraryLanguageCodes] = [languageCode] + } + } // refresh the library let data = try fetchData() @@ -30,24 +38,19 @@ class OPDSRefreshOperation: Operation { try processData(parser: parser) // if library has never been refreshed before, preload wikipedia favicons - if Defaults[.libraryLastRefresh] == nil, let languageCode = Locale.current.languageCode { + if Defaults[.libraryLastRefresh] == nil { (try? Realm())?.objects(ZimFile.self) .filter(NSCompoundPredicate(andPredicateWithSubpredicates: [ NSPredicate(format: "categoryRaw = %@", ZimFile.Category.wikipedia.rawValue), - NSPredicate(format: "languageCode = %@", languageCode), + NSPredicate(format: "languageCode IN %@", Defaults[.libraryLanguageCodes]), NSPredicate(format: "faviconData = nil"), NSPredicate(format: "faviconURL != nil"), ])) .forEach { FaviconDownloadService.shared.download(zimFile: $0) } } + // update last library refresh time DispatchQueue.main.sync { - // if library has never been refreshed before, apply initial language filter - if Defaults[.libraryLastRefresh] == nil, let languageCode = Locale.current.languageCode { - Defaults[.libraryLanguageCodes] = [languageCode] - } - - // update last library refresh time Defaults[.libraryLastRefresh] = Date() } diff --git a/iOS/Controller/Library/LibraryViewController.swift b/iOS/Controller/Library/LibraryViewController.swift index 81f59ef50..acdfa2f79 100644 --- a/iOS/Controller/Library/LibraryViewController.swift +++ b/iOS/Controller/Library/LibraryViewController.swift @@ -94,7 +94,7 @@ class LibraryViewController: UISplitViewController, UISplitViewControllerDelegat let searchText = searchController.searchBar.text DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { guard let searchText = searchText, searchText == searchController.searchBar.text else { return } - self.searchResultsController.rootView.update(searchText) + self.searchResultsController.rootView.viewModel.update(searchText) } } diff --git a/iOS/SwiftUI/Library/LibrarySearchResultView.swift b/iOS/SwiftUI/Library/LibrarySearchResultView.swift index b337e7b14..e4dcde7c3 100644 --- a/iOS/SwiftUI/Library/LibrarySearchResultView.swift +++ b/iOS/SwiftUI/Library/LibrarySearchResultView.swift @@ -6,23 +6,20 @@ // Copyright © 2021 Chris Li. All rights reserved. // +import Combine import SwiftUI import Defaults import RealmSwift @available(iOS 13.0, *) struct LibrarySearchResultView: View { - @ObservedResults( - ZimFile.self, - configuration: Realm.defaultConfig, - sortDescriptor: SortDescriptor(keyPath: "creationDate", ascending: false) - ) private var zimFiles + @ObservedObject private(set) var viewModel = ViewModel() var zimFileSelected: (String, String) -> Void = { _, _ in } var body: some View { List { - ForEach(zimFiles) { zimFile in + ForEach(viewModel.zimFiles) { zimFile in Button(action: { zimFileSelected(zimFile.fileID, zimFile.title) }, label: { ZimFileCell(zimFile) }) @@ -32,17 +29,42 @@ struct LibrarySearchResultView: View { UIApplication.shared.windows.filter{$0.isKeyWindow}.first?.endEditing(false) }) } - - func update(_ searchText: String) { - // update filter - var predicates = [NSPredicate(format: "title CONTAINS[cd] %@", searchText)] - if !Defaults[.libraryLanguageCodes].isEmpty { - predicates.append(NSPredicate(format: "languageCode IN %@", Defaults[.libraryLanguageCodes])) - } - _zimFiles.filter = NSCompoundPredicate(andPredicateWithSubpredicates: predicates) + + class ViewModel: ObservableObject { + @Published private(set) var zimFiles = [ZimFile]() - // download favicons - zimFiles.filter { zimFile in zimFile.faviconData == nil } - .forEach { FaviconDownloadService.shared.download(zimFile: $0) } + private let database = try? Realm() + private let queue = DispatchQueue(label: "org.kiwix.library.category", qos: .userInitiated) + private var collectionSubscriber: AnyCancellable? + + func update(_ searchText: String) { + collectionSubscriber = database?.objects(ZimFile.self) + .filter(NSCompoundPredicate(andPredicateWithSubpredicates: [ + NSPredicate(format: "title CONTAINS[cd] %@", searchText), + NSPredicate(format: "languageCode IN %@", Defaults[.libraryLanguageCodes]) + ])) + .sorted(by: [ + SortDescriptor(keyPath: "title", ascending: true), + SortDescriptor(keyPath: "size", ascending: false) + ]) + .collectionPublisher + .subscribe(on: queue) + .freeze() + .throttle(for: 0.2, scheduler: queue, latest: true) + .map { Array($0) } + .receive(on: DispatchQueue.main) + .catch { _ in Just(([ZimFile]())) } + .sink(receiveValue: { zimFiles in + self.zimFiles = zimFiles + }) + database?.objects(ZimFile.self) + .filter(NSCompoundPredicate(andPredicateWithSubpredicates: [ + NSPredicate(format: "title CONTAINS[cd] %@", searchText), + NSPredicate(format: "languageCode IN %@", Defaults[.libraryLanguageCodes]), + NSPredicate(format: "faviconData = nil"), + NSPredicate(format: "faviconURL != nil"), + ])) + .forEach { FaviconDownloadService.shared.download(zimFile: $0) } + } } }