mirror of
https://github.com/kiwix/kiwix-apple.git
synced 2025-09-09 04:06:57 -04:00
V3.0 bug fixes (#472)
* fix: bookmarks not removed when unlinking zim file * fix: zim file not deleted when deleting zim file * fix: pop view on unlink / delete * build number * webview loading concurrency issue * build number * fix: search text & results cleared when hiding keyboard * fix: some zim files (e.g. ifixit) are missing category * disable random & main article button when no opened zim file * iPadOS: multi window * build number * macOS: navigation item keyboard shortcut * build number * another attempt at fixing crashes when attempting to sending data back at WKURLSchemeTask * build number * Revert "another attempt at fixing crashes when attempting to sending data back at WKURLSchemeTask" This reverts commit cf698483727268a1b1467cb6222b7f038d19d6df. * ignore NSExceptions * resolve compile warning * update to xcode recommended project settings * build number * more deterministic sorting * remove duplicated search texts * build number * build number
This commit is contained in:
parent
70a571277d
commit
baeb36eebb
@ -310,7 +310,7 @@
|
||||
97F425C227151A0D00D0F738 /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97F425C127151A0D00D0F738 /* QuickLook.framework */; };
|
||||
97F425C527151A0D00D0F738 /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97F425C427151A0D00D0F738 /* PreviewViewController.swift */; };
|
||||
97F425CA27151A0D00D0F738 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97F425C827151A0D00D0F738 /* MainInterface.storyboard */; };
|
||||
97F425CE27151A0D00D0F738 /* QuickLookPreview.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 97F425C027151A0D00D0F738 /* QuickLookPreview.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
97F425CE27151A0D00D0F738 /* QuickLookPreview.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 97F425C027151A0D00D0F738 /* QuickLookPreview.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
97F425D327151E9C00D0F738 /* ZimFileService.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9779A5A92456793500F6F6FF /* ZimFileService.mm */; };
|
||||
97F425D427151E9C00D0F738 /* ZimFileService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9779A5AA2456793500F6F6FF /* ZimFileService.swift */; };
|
||||
97F425D527151EF800D0F738 /* ZimFileMetaData.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9779A5AC2456793600F6F6FF /* ZimFileMetaData.mm */; };
|
||||
@ -374,15 +374,15 @@
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C57602202CB0E900E37502 /* Embed App Extensions */ = {
|
||||
97C57602202CB0E900E37502 /* Embed Foundation Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
97F425CE27151A0D00D0F738 /* QuickLookPreview.appex in Embed App Extensions */,
|
||||
97F425CE27151A0D00D0F738 /* QuickLookPreview.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed App Extensions";
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
@ -1206,7 +1206,7 @@
|
||||
97A36C261F8C21210079B452 /* Sources */,
|
||||
97A36C341F8C21210079B452 /* Frameworks */,
|
||||
97A36C431F8C21210079B452 /* Resources */,
|
||||
97C57602202CB0E900E37502 /* Embed App Extensions */,
|
||||
97C57602202CB0E900E37502 /* Embed Foundation Extensions */,
|
||||
977BD604276EA929004D2C32 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
@ -1287,7 +1287,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1330;
|
||||
LastUpgradeCheck = 1310;
|
||||
LastUpgradeCheck = 1410;
|
||||
ORGANIZATIONNAME = "Chris Li";
|
||||
TargetAttributes = {
|
||||
970EC3F023BE8E20008DCA27 = {
|
||||
@ -1900,7 +1900,7 @@
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 99;
|
||||
CURRENT_PROJECT_VERSION = 107;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"SwiftUI/Support\"";
|
||||
DEVELOPMENT_TEAM = L7HWM3SP3L;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@ -1918,7 +1918,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.0;
|
||||
MARKETING_VERSION = 3.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = self.Kiwix;
|
||||
@ -1940,7 +1940,7 @@
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 99;
|
||||
CURRENT_PROJECT_VERSION = 107;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"SwiftUI/Support\"";
|
||||
DEVELOPMENT_TEAM = L7HWM3SP3L;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@ -1958,7 +1958,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.0;
|
||||
MARKETING_VERSION = 3.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = self.Kiwix;
|
||||
PRODUCT_NAME = Kiwix;
|
||||
@ -1987,7 +1987,7 @@
|
||||
"$(LD_RUNPATH_SEARCH_PATHS_$(IS_MACCATALYST))",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
MARKETING_VERSION = 2.1.2;
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.kiwix.KiwixMacOS;
|
||||
@ -2019,7 +2019,7 @@
|
||||
"$(LD_RUNPATH_SEARCH_PATHS_$(IS_MACCATALYST))",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
MARKETING_VERSION = 2.1.2;
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.kiwix.KiwixMacOS;
|
||||
@ -2080,8 +2080,8 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_SWIFT_FLAGS = "";
|
||||
@ -2135,8 +2135,8 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
@ -2287,7 +2287,8 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 99;
|
||||
CURRENT_PROJECT_VERSION = 107;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"SwiftUI/Support\"";
|
||||
DEVELOPMENT_TEAM = L7HWM3SP3L;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
@ -2301,7 +2302,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MARKETING_VERSION = 2.0;
|
||||
MARKETING_VERSION = 3.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = self.Kiwix;
|
||||
@ -2327,7 +2328,8 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 99;
|
||||
CURRENT_PROJECT_VERSION = 107;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"SwiftUI/Support\"";
|
||||
DEVELOPMENT_TEAM = L7HWM3SP3L;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
@ -2341,7 +2343,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MARKETING_VERSION = 2.0;
|
||||
MARKETING_VERSION = 3.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = self.Kiwix;
|
||||
PRODUCT_NAME = Kiwix;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1330"
|
||||
LastUpgradeVersion = "1410"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1310"
|
||||
LastUpgradeVersion = "1410"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1310"
|
||||
LastUpgradeVersion = "1410"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1310"
|
||||
LastUpgradeVersion = "1410"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1310"
|
||||
LastUpgradeVersion = "1410"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1330"
|
||||
LastUpgradeVersion = "1410"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -138,7 +138,7 @@ extension Realm {
|
||||
guard let zimFileData = existingZimFiles.popLast() else { continue }
|
||||
|
||||
zimFile.articleCount = zimFileData.articleCount
|
||||
zimFile.category = zimFileData.category
|
||||
zimFile.category = (Category(rawValue: zimFileData.category) ?? .other).rawValue
|
||||
zimFile.created = zimFileData.created
|
||||
zimFile.downloadURL = zimFileData.downloadURL
|
||||
zimFile.faviconData = zimFileData.faviconData
|
||||
|
@ -7,3 +7,14 @@
|
||||
#import "ZimFileMetaData.h"
|
||||
#import "SearchOperation.h"
|
||||
#import "SearchResult.h"
|
||||
|
||||
|
||||
NS_INLINE NSException * _Nullable objCTryBlock(void(^_Nonnull tryBlock)(void)) {
|
||||
@try {
|
||||
tryBlock();
|
||||
return nil;
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
return exception;
|
||||
}
|
||||
}
|
||||
|
@ -10,59 +10,54 @@ import os
|
||||
import WebKit
|
||||
|
||||
class KiwixURLSchemeHandler: NSObject, WKURLSchemeHandler {
|
||||
private var activeRequests = Set<URLRequest>()
|
||||
private let activeRequestsSemaphore = DispatchSemaphore(value: 1)
|
||||
private let dataFetchingSemaphore = DispatchSemaphore(value: ProcessInfo.processInfo.activeProcessorCount)
|
||||
private var urls = Set<URL>()
|
||||
private var queue = DispatchQueue(label: "org.kiwix.webContent", qos: .userInitiated)
|
||||
|
||||
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
|
||||
// unpack zimFileID and content path from the url
|
||||
guard let url = urlSchemeTask.request.url,
|
||||
url.isKiwixURL else {
|
||||
queue.async {
|
||||
// unpack zimFileID and content path from the url
|
||||
guard let url = urlSchemeTask.request.url, url.isKiwixURL else {
|
||||
urlSchemeTask.didFailWithError(URLError(.unsupportedURL))
|
||||
return
|
||||
}
|
||||
|
||||
// remember this active url scheme task
|
||||
activeRequestsSemaphore.wait()
|
||||
activeRequests.insert(urlSchemeTask.request)
|
||||
activeRequestsSemaphore.signal()
|
||||
|
||||
// fetch data and send response
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
// fetch data
|
||||
self.dataFetchingSemaphore.wait()
|
||||
let content = ZimFileService.shared.getURLContent(url: url)
|
||||
self.dataFetchingSemaphore.signal()
|
||||
|
||||
// check the url scheme task is not stopped
|
||||
self.activeRequestsSemaphore.wait()
|
||||
guard let _ = self.activeRequests.remove(urlSchemeTask.request) else {
|
||||
self.activeRequestsSemaphore.signal()
|
||||
return
|
||||
}
|
||||
self.activeRequestsSemaphore.signal()
|
||||
|
||||
// assemble and send response
|
||||
if let content = content,
|
||||
let response = HTTPURLResponse(
|
||||
url: url,
|
||||
statusCode: 200,
|
||||
httpVersion: "HTTP/1.1",
|
||||
headerFields: ["Content-Type": content.mime, "Content-Length": "\(content.length)"])
|
||||
{
|
||||
urlSchemeTask.didReceive(response)
|
||||
urlSchemeTask.didReceive(content.data)
|
||||
urlSchemeTask.didFinish()
|
||||
} else {
|
||||
os_log("Resource not found for url: %s.", log: Log.URLSchemeHandler, type: .info, url.absoluteString)
|
||||
urlSchemeTask.didFailWithError(URLError(.resourceUnavailable, userInfo: ["url": url]))
|
||||
// fetch url content and return data to the task
|
||||
self.urls.insert(url)
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
let content = ZimFileService.shared.getURLContent(url: url)
|
||||
self.queue.async {
|
||||
guard self.urls.contains(url) else { return }
|
||||
if let content = content,
|
||||
let response = HTTPURLResponse(
|
||||
url: url,
|
||||
statusCode: 200,
|
||||
httpVersion: "HTTP/1.1",
|
||||
headerFields: ["Content-Type": content.mime, "Content-Length": "\(content.length)"])
|
||||
{
|
||||
objCTryBlock {
|
||||
urlSchemeTask.didReceive(response)
|
||||
urlSchemeTask.didReceive(content.data)
|
||||
urlSchemeTask.didFinish()
|
||||
}
|
||||
} else {
|
||||
os_log(
|
||||
"Resource not found for url: %s.",
|
||||
log: Log.URLSchemeHandler,
|
||||
type: .info,
|
||||
url.absoluteString
|
||||
)
|
||||
urlSchemeTask.didFailWithError(URLError(.resourceUnavailable, userInfo: ["url": url]))
|
||||
}
|
||||
self.urls.remove(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
|
||||
activeRequestsSemaphore.wait()
|
||||
activeRequests.remove(urlSchemeTask.request)
|
||||
activeRequestsSemaphore.signal()
|
||||
queue.async {
|
||||
guard let url = urlSchemeTask.request.url else { return }
|
||||
self.urls.remove(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import Defaults
|
||||
struct ZimFileDetail: View {
|
||||
@Binding var url: URL?
|
||||
@Default(.downloadUsingCellular) private var downloadUsingCellular
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
@EnvironmentObject private var viewModel: ViewModel
|
||||
@EnvironmentObject private var libraryViewModel: LibraryViewModel
|
||||
@ObservedObject var zimFile: ZimFile
|
||||
@ -126,6 +127,9 @@ struct ZimFileDetail: View {
|
||||
"""),
|
||||
primaryButton: .destructive(Text("Unlink")) {
|
||||
LibraryOperations.unlink(zimFileID: zimFile.fileID)
|
||||
#if os(iOS)
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
#endif
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
@ -141,6 +145,9 @@ struct ZimFileDetail: View {
|
||||
message: Text("The zim file and all bookmarked articles linked to this zim file will be deleted."),
|
||||
primaryButton: .destructive(Text("Delete")) {
|
||||
LibraryOperations.delete(zimFileID: zimFile.fileID)
|
||||
#if os(iOS)
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
#endif
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
|
@ -80,7 +80,7 @@ class ZimFile: NSManagedObject, Identifiable {
|
||||
@NSManaged var size: Int64
|
||||
|
||||
@NSManaged var downloadTask: DownloadTask?
|
||||
@NSManaged var bookmarks: [Bookmark]
|
||||
@NSManaged var bookmarks: Set<Bookmark>
|
||||
|
||||
static var openedPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||
NSPredicate(format: "fileURLBookmark != nil"),
|
||||
|
@ -94,7 +94,7 @@ struct LibraryOperations {
|
||||
/// Configure a zim file object based on its metadata.
|
||||
static func configureZimFile(_ zimFile: ZimFile, metadata: ZimFileMetaData) {
|
||||
zimFile.articleCount = metadata.articleCount.int64Value
|
||||
zimFile.category = metadata.category
|
||||
zimFile.category = (Category(rawValue: metadata.category) ?? .other).rawValue
|
||||
zimFile.created = metadata.creationDate
|
||||
zimFile.fileDescription = metadata.fileDescription
|
||||
zimFile.fileID = metadata.fileID
|
||||
@ -116,20 +116,22 @@ struct LibraryOperations {
|
||||
|
||||
//MARK: - Deletion
|
||||
|
||||
/// Unlink a zim file from library, and delete the file.
|
||||
/// Unlink a zim file from library, delete associated bookmarks, and delete the file.
|
||||
/// - Parameter zimFile: the zim file to delete
|
||||
static func delete(zimFileID: UUID) {
|
||||
guard let url = ZimFileService.shared.getFileURL(zimFileID: zimFileID) else { return }
|
||||
defer { try? FileManager.default.removeItem(at: url) }
|
||||
LibraryOperations.unlink(zimFileID: zimFileID)
|
||||
}
|
||||
|
||||
/// Unlink a zim file from library, but don't delete the file.
|
||||
/// Unlink a zim file from library, delete associated bookmarks, but don't delete the file.
|
||||
/// - Parameter zimFile: the zim file to unlink
|
||||
static func unlink(zimFileID: UUID) {
|
||||
ZimFileService.shared.close(fileID: zimFileID)
|
||||
|
||||
Database.shared.container.performBackgroundTask { context in
|
||||
context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
|
||||
guard let zimFile = try? ZimFile.fetchRequest(fileID: zimFileID).execute().first else { return }
|
||||
zimFile.bookmarks.forEach { context.delete($0) }
|
||||
if zimFile.downloadURL == nil {
|
||||
context.delete(zimFile)
|
||||
} else {
|
||||
|
@ -59,14 +59,17 @@ extension SearchOperation {
|
||||
}
|
||||
}
|
||||
|
||||
// sort the results
|
||||
guard !isCancelled else { return }
|
||||
__results.sort { lhs, rhs in
|
||||
guard let lhs = (lhs as? SearchResult)?.score?.doubleValue,
|
||||
let rhs = (rhs as? SearchResult)?.score?.doubleValue else { return .orderedSame }
|
||||
if lhs < rhs {
|
||||
return .orderedAscending
|
||||
} else if lhs > rhs {
|
||||
return .orderedDescending
|
||||
guard let lhs = lhs as? SearchResult,
|
||||
let rhs = rhs as? SearchResult,
|
||||
let lhsScore = lhs.score?.doubleValue,
|
||||
let rhsScore = rhs.score?.doubleValue else { return .orderedSame }
|
||||
if lhsScore != rhsScore {
|
||||
return lhsScore < rhsScore ? .orderedAscending : .orderedDescending
|
||||
} else if let lhsSnippet = lhs.snippet, let rhsSnippet = rhs.snippet {
|
||||
return lhsSnippet.length > rhsSnippet.length ? .orderedAscending : .orderedDescending
|
||||
} else {
|
||||
return .orderedSame
|
||||
}
|
||||
|
@ -75,6 +75,8 @@ struct RootViewV1: UIViewControllerRepresentable {
|
||||
if isSearchActive {
|
||||
searchBar.text = searchViewModel.searchText
|
||||
} else {
|
||||
searchBar.text = ""
|
||||
|
||||
// Triggers "AttributeGraph: cycle detected through attribute" if not dispatched (iOS 16.0 SDK)
|
||||
DispatchQueue.main.async {
|
||||
searchBar.resignFirstResponder()
|
||||
@ -102,11 +104,6 @@ struct RootViewV1: UIViewControllerRepresentable {
|
||||
rootView.isSearchActive = true
|
||||
}
|
||||
}
|
||||
|
||||
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
|
||||
searchBar.text = ""
|
||||
rootView.searchViewModel.searchText = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,7 +111,7 @@ private struct Content: View {
|
||||
@Binding var isSearchActive: Bool
|
||||
@Binding var url: URL?
|
||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
||||
@EnvironmentObject var viewModel: ReadingViewModel
|
||||
@EnvironmentObject var searchViewModel: SearchViewModel
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
@ -155,6 +152,7 @@ private struct Content: View {
|
||||
SettingsButton()
|
||||
} else if isSearchActive {
|
||||
Button("Cancel") {
|
||||
searchViewModel.searchText = ""
|
||||
isSearchActive = false
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key></key>
|
||||
<string></string>
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>org.kiwix.library_refresh</string>
|
||||
@ -32,6 +34,13 @@
|
||||
</array>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<true/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict/>
|
||||
</dict>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
|
@ -40,6 +40,8 @@
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
@ -44,7 +44,7 @@ class SearchViewModel: NSObject, ObservableObject, NSFetchedResultsControllerDel
|
||||
fetchedResultsController.delegate = self
|
||||
|
||||
// subscribers
|
||||
searchSubscriber = Publishers.CombineLatest($searchText, $zimFiles)
|
||||
searchSubscriber = Publishers.CombineLatest($searchText.removeDuplicates(), $zimFiles)
|
||||
.map { [unowned self] searchText, zimFiles in
|
||||
self.inProgress = true
|
||||
return (searchText, zimFiles)
|
||||
|
@ -61,6 +61,8 @@ class ViewModel: NSObject, ObservableObject, WKNavigationDelegate, WKUIDelegate
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
|
||||
let error = error as NSError
|
||||
guard error.code != NSURLErrorCancelled else { return }
|
||||
activeAlert = .articleFailedToLoad
|
||||
}
|
||||
|
||||
|
@ -132,6 +132,7 @@ struct LibraryButton: View {
|
||||
|
||||
struct MainArticleButton: View {
|
||||
@Binding var url: URL?
|
||||
@FetchRequest(sortDescriptors: [], predicate: ZimFile.openedPredicate) private var zimFiles: FetchedResults<ZimFile>
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
@ -139,7 +140,9 @@ struct MainArticleButton: View {
|
||||
url = ZimFileService.shared.getMainPageURL(zimFileID: zimFileID)
|
||||
} label: {
|
||||
Label("Main Article", systemImage: "house")
|
||||
}.help("Show main article")
|
||||
}
|
||||
.disabled(zimFiles.isEmpty)
|
||||
.help("Show main article")
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,7 +166,9 @@ struct MainArticleMenu: View {
|
||||
} primaryAction: {
|
||||
let zimFileID = UUID(uuidString: url?.host ?? "")
|
||||
url = ZimFileService.shared.getMainPageURL(zimFileID: zimFileID)
|
||||
}.help("Show main article")
|
||||
}
|
||||
.disabled(zimFiles.isEmpty)
|
||||
.help("Show main article")
|
||||
}
|
||||
}
|
||||
|
||||
@ -289,6 +294,7 @@ struct PageZoomButtons: View {
|
||||
|
||||
struct RandomArticleButton: View {
|
||||
@Binding var url: URL?
|
||||
@FetchRequest(sortDescriptors: [], predicate: ZimFile.openedPredicate) private var zimFiles: FetchedResults<ZimFile>
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
@ -296,7 +302,9 @@ struct RandomArticleButton: View {
|
||||
url = ZimFileService.shared.getRandomPageURL(zimFileID: zimFileID)
|
||||
} label: {
|
||||
Label("Random Article", systemImage: "die.face.5")
|
||||
}.help("Show random article")
|
||||
}
|
||||
.disabled(zimFiles.isEmpty)
|
||||
.help("Show random article")
|
||||
}
|
||||
}
|
||||
|
||||
@ -320,7 +328,9 @@ struct RandomArticleMenu: View {
|
||||
} primaryAction: {
|
||||
let zimFileID = UUID(uuidString: url?.host ?? "")
|
||||
url = ZimFileService.shared.getRandomPageURL(zimFileID: zimFileID)
|
||||
}.help("Show random article")
|
||||
}
|
||||
.disabled(zimFiles.isEmpty)
|
||||
.help("Show random article")
|
||||
}
|
||||
}
|
||||
|
||||
@ -354,17 +364,17 @@ struct SidebarNavigationItemButtons: View {
|
||||
@FocusedBinding(\.navigationItem) var navigationItem: NavigationItem??
|
||||
|
||||
var body: some View {
|
||||
buildButtons([.reading, .bookmarks], keyboardShortcutOffset: 1)
|
||||
buildButtons([.reading, .bookmarks], modifiers: [.command])
|
||||
Divider()
|
||||
buildButtons([.opened, .categories, .downloads, .new], keyboardShortcutOffset: 3)
|
||||
buildButtons([.opened, .categories, .downloads, .new], modifiers: [.command, .control])
|
||||
}
|
||||
|
||||
private func buildButtons(_ navigationItems: [NavigationItem], keyboardShortcutOffset: Int) -> some View {
|
||||
private func buildButtons(_ navigationItems: [NavigationItem], modifiers: EventModifiers = []) -> some View {
|
||||
ForEach(Array(navigationItems.enumerated()), id: \.element) { index, item in
|
||||
Button(item.name) {
|
||||
navigationItem = item
|
||||
}
|
||||
.keyboardShortcut(KeyEquivalent(Character("\(index + keyboardShortcutOffset)")))
|
||||
.keyboardShortcut(KeyEquivalent(Character("\(index + 1)")), modifiers: modifiers)
|
||||
.disabled(navigationItem == nil)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user