mirror of
https://github.com/kiwix/kiwix-apple.git
synced 2025-09-26 21:39:37 -04:00
Merge pull request #576 from kiwix/feature/520-custom-app-support-v2
Add custom app support, fixing all the core issues in apple code baseCustom app support
This commit is contained in:
commit
b4c944e96d
@ -1,4 +1,3 @@
|
||||
//
|
||||
// App_iOS.swift
|
||||
// Kiwix
|
||||
//
|
||||
@ -21,13 +20,8 @@ struct Kiwix: App {
|
||||
|
||||
init() {
|
||||
fileMonitor = DirectoryMonitor(url: URL.documentDirectory) { LibraryOperations.scanDirectory($0) }
|
||||
UNUserNotificationCenter.current().delegate = appDelegate
|
||||
LibraryOperations.reopen()
|
||||
LibraryOperations.scanDirectory(URL.documentDirectory)
|
||||
LibraryOperations.applyFileBackupSetting()
|
||||
LibraryOperations.registerBackgroundTask()
|
||||
LibraryOperations.applyLibraryAutoRefreshSetting()
|
||||
DownloadService.shared.restartHeartbeatIfNeeded()
|
||||
UNUserNotificationCenter.current().delegate = appDelegate
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
@ -50,6 +44,23 @@ struct Kiwix: App {
|
||||
NotificationCenter.openURL(url)
|
||||
}
|
||||
}
|
||||
.task {
|
||||
switch AppType.current {
|
||||
case .kiwix:
|
||||
fileMonitor.start()
|
||||
LibraryOperations.reopen {
|
||||
navigation.navigateToMostRecentTab()
|
||||
}
|
||||
LibraryOperations.scanDirectory(URL.documentDirectory)
|
||||
LibraryOperations.applyFileBackupSetting()
|
||||
LibraryOperations.applyLibraryAutoRefreshSetting()
|
||||
DownloadService.shared.restartHeartbeatIfNeeded()
|
||||
case let .custom(zimFileURL):
|
||||
LibraryOperations.open(url: zimFileURL) {
|
||||
navigation.navigateToMostRecentTab()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.commands {
|
||||
CommandGroup(replacing: .undoRedo) {
|
||||
|
@ -10,6 +10,7 @@ import SwiftUI
|
||||
import UserNotifications
|
||||
import Combine
|
||||
import Defaults
|
||||
import CoreKiwix
|
||||
|
||||
#if os(macOS)
|
||||
@main
|
||||
@ -19,10 +20,6 @@ struct Kiwix: App {
|
||||
|
||||
init() {
|
||||
UNUserNotificationCenter.current().delegate = notificationCenterDelegate
|
||||
LibraryOperations.reopen()
|
||||
LibraryOperations.scanDirectory(URL.documentDirectory)
|
||||
LibraryOperations.applyFileBackupSetting()
|
||||
DownloadService.shared.restartHeartbeatIfNeeded()
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
@ -57,11 +54,13 @@ struct Kiwix: App {
|
||||
Settings {
|
||||
TabView {
|
||||
ReadingSettings()
|
||||
LibrarySettings()
|
||||
if FeatureFlags.hasLibrary {
|
||||
LibrarySettings()
|
||||
.environmentObject(libraryRefreshViewModel)
|
||||
}
|
||||
About()
|
||||
}
|
||||
.frame(width: 550, height: 400)
|
||||
.environmentObject(libraryRefreshViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,9 +94,11 @@ struct RootView: View {
|
||||
ForEach(primaryItems, id: \.self) { navigationItem in
|
||||
Label(navigationItem.name.localized, systemImage: navigationItem.icon)
|
||||
}
|
||||
Section("Library".localized) {
|
||||
ForEach(libraryItems, id: \.self) { navigationItem in
|
||||
Label(navigationItem.name.localized, systemImage: navigationItem.icon)
|
||||
if FeatureFlags.hasLibrary {
|
||||
Section("Library".localized) {
|
||||
ForEach(libraryItems, id: \.self) { navigationItem in
|
||||
Label(navigationItem.name.localized, systemImage: navigationItem.icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -111,6 +112,8 @@ struct RootView: View {
|
||||
}.help("Show sidebar".localized)
|
||||
}
|
||||
switch navigation.currentItem {
|
||||
case .loading:
|
||||
LoadingView()
|
||||
case .reading:
|
||||
BrowserTab().environmentObject(browser)
|
||||
.withHostingWindow { window in
|
||||
@ -152,6 +155,20 @@ struct RootView: View {
|
||||
}
|
||||
.onReceive(appTerminates) { _ in
|
||||
browser.persistAllTabIdsFromWindows()
|
||||
}.task {
|
||||
switch AppType.current {
|
||||
case .kiwix:
|
||||
LibraryOperations.reopen {
|
||||
navigation.currentItem = .reading
|
||||
}
|
||||
LibraryOperations.scanDirectory(URL.documentDirectory)
|
||||
LibraryOperations.applyFileBackupSetting()
|
||||
DownloadService.shared.restartHeartbeatIfNeeded()
|
||||
case let .custom(zimFileURL):
|
||||
LibraryOperations.open(url: zimFileURL) {
|
||||
navigation.currentItem = .reading
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ class CompactViewController: UIHostingController<AnyView>, UISearchControllerDel
|
||||
apperance.configureWithDefaultBackground()
|
||||
return apperance
|
||||
}()
|
||||
searchController.searchBar.autocorrectionType = .no
|
||||
navigationItem.titleView = searchController.searchBar
|
||||
searchController.automaticallyShowsCancelButton = false
|
||||
searchController.delegate = self
|
||||
@ -54,10 +55,6 @@ class CompactViewController: UIHostingController<AnyView>, UISearchControllerDel
|
||||
guard self?.searchController.searchBar.text != searchText else { return }
|
||||
self?.searchController.searchBar.text = searchText
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
openURLObserver = NotificationCenter.default.addObserver(
|
||||
forName: .openURL, object: nil, queue: nil
|
||||
) { [weak self] _ in
|
||||
@ -65,12 +62,11 @@ class CompactViewController: UIHostingController<AnyView>, UISearchControllerDel
|
||||
self?.navigationItem.setRightBarButton(nil, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
|
||||
func willPresentSearchController(_ searchController: UISearchController) {
|
||||
navigationController?.setToolbarHidden(true, animated: true)
|
||||
navigationItem.setRightBarButton(
|
||||
|
@ -43,6 +43,14 @@ class SidebarViewController: UICollectionViewController, NSFetchedResultsControl
|
||||
case tabs
|
||||
case library
|
||||
case settings
|
||||
|
||||
static var allSections: [Section] {
|
||||
if FeatureFlags.hasLibrary {
|
||||
allCases
|
||||
} else {
|
||||
allCases.filter { $0 != .library }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
@ -77,7 +85,7 @@ class SidebarViewController: UICollectionViewController, NSFetchedResultsControl
|
||||
fetchedResultController.delegate = self
|
||||
|
||||
// configure view
|
||||
navigationItem.title = "Kiwix"
|
||||
navigationItem.title = Brand.appName
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
image: UIImage(systemName: "plus.square"),
|
||||
primaryAction: UIAction { [unowned self] _ in
|
||||
@ -107,9 +115,11 @@ class SidebarViewController: UICollectionViewController, NSFetchedResultsControl
|
||||
|
||||
// apply initial snapshot
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, NavigationItem>()
|
||||
snapshot.appendSections(Section.allCases)
|
||||
snapshot.appendSections(Section.allSections)
|
||||
snapshot.appendItems([.bookmarks], toSection: .primary)
|
||||
snapshot.appendItems([.opened, .categories, .downloads, .new], toSection: .library)
|
||||
if FeatureFlags.hasLibrary {
|
||||
snapshot.appendItems([.opened, .categories, .downloads, .new], toSection: .library)
|
||||
}
|
||||
snapshot.appendItems([.settings], toSection: .settings)
|
||||
dataSource.apply(snapshot, animatingDifferences: false)
|
||||
try? fetchedResultController.performFetch()
|
||||
@ -184,7 +194,7 @@ class SidebarViewController: UICollectionViewController, NSFetchedResultsControl
|
||||
}
|
||||
|
||||
private func configureHeader(headerView: UICollectionViewListCell, elementKind: String, indexPath: IndexPath) {
|
||||
let section = Section.allCases[indexPath.section]
|
||||
let section = Section.allSections[indexPath.section]
|
||||
switch section {
|
||||
case .tabs:
|
||||
var config = UIListContentConfiguration.sidebarHeader()
|
||||
|
@ -11,7 +11,7 @@ import Combine
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
class SplitViewController: UISplitViewController {
|
||||
final class SplitViewController: UISplitViewController {
|
||||
let navigationViewModel: NavigationViewModel
|
||||
private var navigationItemObserver: AnyCancellable?
|
||||
private var openURLObserver: NSObjectProtocol?
|
||||
@ -127,6 +127,9 @@ class SplitViewController: UISplitViewController {
|
||||
case .settings:
|
||||
let controller = UIHostingController(rootView: Settings())
|
||||
setViewController(UINavigationController(rootViewController: controller), for: .secondary)
|
||||
case .loading:
|
||||
let controller = UIHostingController(rootView: LoadingView())
|
||||
setViewController(UINavigationController(rootViewController: controller), for: .secondary)
|
||||
default:
|
||||
let controller = UIHostingController(rootView: Text("Not yet implemented".localized))
|
||||
setViewController(UINavigationController(rootViewController: controller), for: .secondary)
|
||||
|
@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 55;
|
||||
objectVersion = 60;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@ -44,7 +44,6 @@
|
||||
973A0DE8281DD7EB00B41E71 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9779A7E224567A5A00F6F6FF /* Log.swift */; };
|
||||
973A0DEB281DDBB600B41E71 /* ZimFilesDownloads.swift in Sources */ = {isa = PBXBuildFile; fileRef = 973A0DE9281DDBB600B41E71 /* ZimFilesDownloads.swift */; };
|
||||
973A0DF1282E981200B41E71 /* ZimFileRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 973A0DEF282E981200B41E71 /* ZimFileRow.swift */; };
|
||||
973A0DF72830929C00B41E71 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97E94B1D271EF250005B0295 /* Assets.xcassets */; };
|
||||
973A0DFD283100C300B41E71 /* ZimFilesOpened.swift in Sources */ = {isa = PBXBuildFile; fileRef = 973A0DFB283100C300B41E71 /* ZimFilesOpened.swift */; };
|
||||
973A0E032831057200B41E71 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97B3BACD2736CE3500A23F49 /* URL.swift */; };
|
||||
9744068728CE263800916BD4 /* DirectoryMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9779A5CF2456796A00F6F6FF /* DirectoryMonitor.swift */; };
|
||||
@ -99,9 +98,13 @@
|
||||
97DE2BA3283A8E5C00C63D9B /* LibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97DE2BA1283A8E5C00C63D9B /* LibraryViewModel.swift */; };
|
||||
97DE2BA6283A944100C63D9B /* GridCommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97DE2BA4283A944100C63D9B /* GridCommon.swift */; };
|
||||
97DE2BAD283B133700C63D9B /* wikipedia_dark.css in Resources */ = {isa = PBXBuildFile; fileRef = 970885D0271339A300C5795C /* wikipedia_dark.css */; };
|
||||
97E88F4D2AE407350037F0E5 /* CoreKiwix.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97E88F4C2AE407320037F0E5 /* CoreKiwix.xcframework */; };
|
||||
97E88F4D2AE407350037F0E5 /* CoreKiwix.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97E88F4C2AE407320037F0E5 /* CoreKiwix.xcframework */; settings = {ATTRIBUTES = (Required, ); }; };
|
||||
97F3333028AFC1A2007FF53C /* SearchResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97F3332E28AFC1A2007FF53C /* SearchResults.swift */; };
|
||||
97FB4ECE28B4E221003FB524 /* SwiftUIBackports in Frameworks */ = {isa = PBXBuildFile; productRef = 97FB4ECD28B4E221003FB524 /* SwiftUIBackports */; };
|
||||
980179512B0DF42100E8E1E2 /* Brand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980179502B0DF42100E8E1E2 /* Brand.swift */; };
|
||||
980179542B0E402C00E8E1E2 /* kiwix.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 980179532B0E402C00E8E1E2 /* kiwix.xcconfig */; };
|
||||
983A3F562B129F6B000E7A51 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 983A3F552B129F6B000E7A51 /* Assets.xcassets */; };
|
||||
983D22672B16ABB6005EBAF1 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 983D22662B16ABB6005EBAF1 /* LoadingView.swift */; };
|
||||
983ED7192B08AFE700409078 /* Kiwix-Bridging-Header.h in Sources */ = {isa = PBXBuildFile; fileRef = 9779A5D02456796A00F6F6FF /* Kiwix-Bridging-Header.h */; platformFilter = ios; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@ -234,13 +237,16 @@
|
||||
97DE2BA1283A8E5C00C63D9B /* LibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryViewModel.swift; sourceTree = "<group>"; };
|
||||
97DE2BA4283A944100C63D9B /* GridCommon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridCommon.swift; sourceTree = "<group>"; };
|
||||
97E88F4C2AE407320037F0E5 /* CoreKiwix.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = CoreKiwix.xcframework; sourceTree = SOURCE_ROOT; };
|
||||
97E94B1D271EF250005B0295 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97E94B22271EF250005B0295 /* Kiwix.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Kiwix.entitlements; sourceTree = "<group>"; };
|
||||
97F3332E28AFC1A2007FF53C /* SearchResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResults.swift; sourceTree = "<group>"; };
|
||||
97F425C127151A0D00D0F738 /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = System/Library/Frameworks/QuickLook.framework; sourceTree = SDKROOT; };
|
||||
97F6CC5020BD960F005CDBD2 /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; };
|
||||
97FB4B0A27B819A90055F86E /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = "<group>"; };
|
||||
97FD2F5E251EA07B0034927C /* FeatureFlags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlags.swift; sourceTree = "<group>"; };
|
||||
980179502B0DF42100E8E1E2 /* Brand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Brand.swift; sourceTree = "<group>"; };
|
||||
980179532B0E402C00E8E1E2 /* kiwix.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = kiwix.xcconfig; sourceTree = "<group>"; };
|
||||
983A3F552B129F6B000E7A51 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
983D22662B16ABB6005EBAF1 /* LoadingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -317,6 +323,7 @@
|
||||
974E7EE22930201500BDF59C /* ZimFileService */,
|
||||
9735D0B82775363900C7D495 /* DataModel.xcdatamodeld */,
|
||||
973A0DE5281DC8F400B41E71 /* DownloadService.swift */,
|
||||
980179502B0DF42100E8E1E2 /* Brand.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
@ -407,6 +414,7 @@
|
||||
975088BF287EEE2900273181 /* BuildingBlocks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
983D22662B16ABB6005EBAF1 /* LoadingView.swift */,
|
||||
97486D05284A36790096E4DD /* ArticleCell.swift */,
|
||||
972096E62AE421C300B378B0 /* Attribute.swift */,
|
||||
97341C6C2852248500BC273E /* DownloadTaskCell.swift */,
|
||||
@ -553,7 +561,7 @@
|
||||
97E94B26271EF359005B0295 /* Support */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97E94B1D271EF250005B0295 /* Assets.xcassets */,
|
||||
983A3F552B129F6B000E7A51 /* Assets.xcassets */,
|
||||
9713F7782AE416E5007DD9EC /* CoreKiwix.modulemap */,
|
||||
97B707042974637200562392 /* Info.plist */,
|
||||
9735B88B279D9A74005F0D1A /* injection.js */,
|
||||
@ -561,6 +569,7 @@
|
||||
9779A5D02456796A00F6F6FF /* Kiwix-Bridging-Header.h */,
|
||||
970885D0271339A300C5795C /* wikipedia_dark.css */,
|
||||
8E4396492B02E455007F0BC4 /* Localizable.strings */,
|
||||
980179532B0E402C00E8E1E2 /* kiwix.xcconfig */,
|
||||
);
|
||||
path = Support;
|
||||
sourceTree = "<group>";
|
||||
@ -638,7 +647,7 @@
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastSwiftUpdateCheck = 1420;
|
||||
LastUpgradeCheck = 1500;
|
||||
ORGANIZATIONNAME = "Chris Li";
|
||||
ORGANIZATIONNAME = Kiwix;
|
||||
TargetAttributes = {
|
||||
97008AB92974A5BF0076E60C = {
|
||||
CreatedOnToolsVersion = 14.2;
|
||||
@ -693,9 +702,10 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
973A0DF72830929C00B41E71 /* Assets.xcassets in Resources */,
|
||||
980179542B0E402C00E8E1E2 /* kiwix.xcconfig in Resources */,
|
||||
8E4396462B02E455007F0BC4 /* Localizable.strings in Resources */,
|
||||
979D3A7C284159BF00E396B8 /* injection.js in Resources */,
|
||||
983A3F562B129F6B000E7A51 /* Assets.xcassets in Resources */,
|
||||
97DE2BAD283B133700C63D9B /* wikipedia_dark.css in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -724,6 +734,7 @@
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
@ -820,6 +831,7 @@
|
||||
972DE4B42814A4F6004FD9B9 /* DataModel.xcdatamodeld in Sources */,
|
||||
976BAEBD2849056B0049404F /* SearchOperation.swift in Sources */,
|
||||
976D90E428159BFA00CC7D29 /* Favicon.swift in Sources */,
|
||||
983D22672B16ABB6005EBAF1 /* LoadingView.swift in Sources */,
|
||||
976D90DB281584BF00CC7D29 /* FlavorTag.swift in Sources */,
|
||||
972DE4BD2814A5BE004FD9B9 /* OPDSParser.mm in Sources */,
|
||||
972DE4B52814A502004FD9B9 /* Entities.swift in Sources */,
|
||||
@ -841,6 +853,7 @@
|
||||
973A0DE7281DC8F400B41E71 /* DownloadService.swift in Sources */,
|
||||
9721BBB72841C16D005C910D /* Message.swift in Sources */,
|
||||
97BD6C9D2886E06B004A8532 /* HTMLParser.swift in Sources */,
|
||||
980179512B0DF42100E8E1E2 /* Brand.swift in Sources */,
|
||||
97677B572A8FA80000F523AB /* FileImport.swift in Sources */,
|
||||
972727AA2A89122F00BCAF75 /* App_macOS.swift in Sources */,
|
||||
97DE2BA3283A8E5C00C63D9B /* LibraryViewModel.swift in Sources */,
|
||||
@ -961,6 +974,7 @@
|
||||
};
|
||||
972DE4B12814A3D9004FD9B9 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 980179532B0E402C00E8E1E2 /* kiwix.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
@ -970,13 +984,14 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = Support/Kiwix.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 119;
|
||||
CURRENT_PROJECT_VERSION = 120;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = L7HWM3SP3L;
|
||||
"ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Support/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Kiwix;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.reference";
|
||||
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
||||
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Kiwix needs permission to saves images to your photos app.";
|
||||
@ -989,7 +1004,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.2;
|
||||
MARKETING_VERSION = 3.3;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = self.Kiwix;
|
||||
@ -1007,6 +1022,7 @@
|
||||
};
|
||||
972DE4B22814A3D9004FD9B9 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 980179532B0E402C00E8E1E2 /* kiwix.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
@ -1016,18 +1032,20 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = Support/Kiwix.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 119;
|
||||
CURRENT_PROJECT_VERSION = 120;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = L7HWM3SP3L;
|
||||
"ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Support/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Kiwix;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.reference";
|
||||
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
|
||||
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Kiwix needs permission to saves images to your photos app.";
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchKiwix.storyboard;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
@ -1035,7 +1053,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.2;
|
||||
MARKETING_VERSION = 3.3;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = self.Kiwix;
|
||||
PRODUCT_NAME = Kiwix;
|
||||
|
82
Model/Brand.swift
Normal file
82
Model/Brand.swift
Normal file
@ -0,0 +1,82 @@
|
||||
// Copyright © 2023 Kiwix.
|
||||
|
||||
import Foundation
|
||||
import os
|
||||
|
||||
enum AppType {
|
||||
case kiwix
|
||||
case custom(zimFileURL: URL)
|
||||
|
||||
static let current = AppType()
|
||||
|
||||
static var isCustom: Bool {
|
||||
switch current {
|
||||
case .kiwix: return false
|
||||
case .custom: return true
|
||||
}
|
||||
}
|
||||
|
||||
private init() {
|
||||
guard let zimFileName: String = Config.value(for: .customZimFile),
|
||||
!zimFileName.isEmpty else {
|
||||
// it's not a custom app as it has no zim file set
|
||||
self = .kiwix
|
||||
return
|
||||
}
|
||||
guard let zimURL: URL = Bundle.main.url(forResource: zimFileName, withExtension: "zim") else {
|
||||
fatalError("zim file named: \(zimFileName) cannot be found")
|
||||
}
|
||||
self = .custom(zimFileURL: zimURL)
|
||||
}
|
||||
}
|
||||
|
||||
enum Brand {
|
||||
static let appName: String = Config.value(for: .displayName) ?? "Kiwix"
|
||||
static let appStoreId: String = Config.value(for: .appStoreID) ?? "id997079563"
|
||||
static let welcomeLogoImageName: String = "welcomeLogo"
|
||||
|
||||
static var defaultExternalLinkPolicy: ExternalLinkLoadingPolicy {
|
||||
guard let policyString: String = Config.value(for: .externalLinkDefaultPolicy),
|
||||
let policy = ExternalLinkLoadingPolicy(rawValue: policyString) else {
|
||||
return .alwaysAsk
|
||||
}
|
||||
return policy
|
||||
}
|
||||
|
||||
static var defaultSearchSnippetMode: SearchResultSnippetMode {
|
||||
guard FeatureFlags.showSearchSnippetInSettings else {
|
||||
// for custom apps, where we do not show this in settings, it should be disabled by default
|
||||
return .disabled
|
||||
}
|
||||
return .firstSentence
|
||||
}
|
||||
}
|
||||
|
||||
enum Config: String {
|
||||
|
||||
case appStoreID = "APP_STORE_ID"
|
||||
case displayName = "CFBundleDisplayName"
|
||||
|
||||
// this marks if the app is custom or not
|
||||
case customZimFile = "CUSTOM_ZIM_FILE"
|
||||
case showExternalLinkSettings = "SETTINGS_SHOW_EXTERNAL_LINK_OPTION"
|
||||
case externalLinkDefaultPolicy = "SETTINGS_DEFAULT_EXTERNAL_LINK_TO"
|
||||
case showSearchSnippetInSettings = "SETTINGS_SHOW_SEARCH_SNIPPET"
|
||||
|
||||
static func value<T>(for key: Config) -> T? where T: LosslessStringConvertible {
|
||||
guard let object = Bundle.main.object(forInfoDictionaryKey: key.rawValue) else {
|
||||
os_log("Missing key from bundle: %@", log: Log.Branding, type: .error, key.rawValue)
|
||||
return nil
|
||||
}
|
||||
switch object {
|
||||
case let value as T:
|
||||
return value
|
||||
case let string as String:
|
||||
guard let value = T(string) else { fallthrough }
|
||||
return value
|
||||
default:
|
||||
os_log("Invalid value type found for key: %@", log: Log.Branding, type: .error, key.rawValue)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
@ -8,20 +8,18 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct FeatureFlags {
|
||||
static var wikipediaDarkUserCSS: Bool {
|
||||
#if DEBUG
|
||||
true
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
}
|
||||
|
||||
static var map: Bool {
|
||||
#if DEBUG
|
||||
true
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
}
|
||||
enum FeatureFlags {
|
||||
#if DEBUG
|
||||
static let wikipediaDarkUserCSS: Bool = true
|
||||
static let map: Bool = true
|
||||
#else
|
||||
static let wikipediaDarkUserCSS: Bool = false
|
||||
static let map: Bool = false
|
||||
#endif
|
||||
/// Custom apps, which have a bundled zim file, do not require library access
|
||||
/// this will remove all library related features
|
||||
static let hasLibrary: Bool = !AppType.isCustom
|
||||
|
||||
static let showExternalLinkOptionInSettings: Bool = Config.value(for: .showExternalLinkSettings) ?? true
|
||||
static let showSearchSnippetInSettings: Bool = Config.value(for: .showSearchSnippetInSettings) ?? true
|
||||
}
|
||||
|
@ -17,4 +17,5 @@ struct Log {
|
||||
static let LibraryOperations = OSLog(subsystem: subsystem, category: "LibraryOperations")
|
||||
static let OPDS = OSLog(subsystem: subsystem, category: "OPDS")
|
||||
static let URLSchemeHandler = OSLog(subsystem: subsystem, category: "URLSchemeHandler")
|
||||
static let Branding = OSLog(subsystem: subsystem, category: "Branding")
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
// Created by Chris Li on 11/6/21.
|
||||
// Copyright © 2021 Chris Li. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension URL {
|
||||
@ -22,5 +23,15 @@ extension URL {
|
||||
["http", "https"].contains(scheme)
|
||||
}
|
||||
|
||||
static let documentDirectory = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
|
||||
// swiftlint:disable:next force_try
|
||||
static let documentDirectory = try! FileManager.default.url(
|
||||
for: .documentDirectory,
|
||||
in: .userDomainMask,
|
||||
appropriateFor: nil,
|
||||
create: false
|
||||
)
|
||||
|
||||
init(appStoreReviewForName appName: String, appStoreID: String) {
|
||||
self.init(string: "itms-apps://itunes.apple.com/us/app/\(appName)/\(appStoreID)?action=write-review")!
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
@ -2,8 +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>APP_STORE_ID</key>
|
||||
<string>$(APP_STORE_ID)</string>
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>org.kiwix.library_refresh</string>
|
||||
|
@ -201,3 +201,4 @@ The software as well as the content is free to use for anyone.";
|
||||
"First Paragraph" = "First Paragraph";
|
||||
"First Sentence" = "First Sentence";
|
||||
"Matches" = "Matches";
|
||||
"Loading..." = "Loading...";
|
5
Support/kiwix.xcconfig
Normal file
5
Support/kiwix.xcconfig
Normal file
@ -0,0 +1,5 @@
|
||||
// Copyright © 2023 Kiwix.
|
||||
|
||||
// Configuration settings file format documentation can be found at:
|
||||
// https://help.apple.com/xcode/#/dev745c5c974
|
||||
APP_STORE_ID = id997079563
|
@ -13,10 +13,10 @@ extension Defaults.Keys {
|
||||
static let webViewTextSizeAdjustFactor = Key<Double>("webViewZoomScale", default: 1)
|
||||
static let webViewPageZoom = Key<Double>("webViewPageZoom", default: 1)
|
||||
static let externalLinkLoadingPolicy = Key<ExternalLinkLoadingPolicy>(
|
||||
"externalLinkLoadingPolicy", default: .alwaysAsk
|
||||
"externalLinkLoadingPolicy", default: Brand.defaultExternalLinkPolicy
|
||||
)
|
||||
static let searchResultSnippetMode = Key<SearchResultSnippetMode>(
|
||||
"searchResultSnippetMode", default: .firstSentence
|
||||
"searchResultSnippetMode", default: Brand.defaultSearchSnippetMode
|
||||
)
|
||||
//
|
||||
// // UI
|
||||
|
@ -24,7 +24,6 @@ class DirectoryMonitor {
|
||||
init(url: URL, onChange: ((URL) -> Void)? = nil) {
|
||||
self.url = url
|
||||
self.onChange = onChange
|
||||
start()
|
||||
}
|
||||
|
||||
// MARK: Monitoring
|
||||
|
@ -177,12 +177,15 @@ enum LibraryTabItem: String, CaseIterable, Identifiable {
|
||||
enum NavigationItem: Hashable, Identifiable {
|
||||
var id: Int { hashValue }
|
||||
|
||||
case loading
|
||||
case reading, bookmarks, map(location: CLLocation?), tab(objectID: NSManagedObjectID)
|
||||
case opened, categories, new, downloads
|
||||
case settings
|
||||
|
||||
var name: String {
|
||||
switch self {
|
||||
case .loading:
|
||||
return "Loading"
|
||||
case .reading:
|
||||
return "Reading"
|
||||
case .bookmarks:
|
||||
@ -206,6 +209,8 @@ enum NavigationItem: Hashable, Identifiable {
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .loading:
|
||||
return "loading"
|
||||
case .reading:
|
||||
return "book"
|
||||
case .bookmarks:
|
||||
@ -228,7 +233,6 @@ enum NavigationItem: Hashable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum SearchResultSnippetMode: String, CaseIterable, Identifiable, Defaults.Serializable {
|
||||
case disabled, firstParagraph, firstSentence, matches
|
||||
|
||||
|
@ -6,7 +6,9 @@
|
||||
// Copyright © 2022 Chris Li. All rights reserved.
|
||||
//
|
||||
|
||||
class Formatter {
|
||||
import Foundation
|
||||
|
||||
enum Formatter {
|
||||
static let dateShort: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .short
|
||||
@ -45,7 +47,7 @@ class Formatter {
|
||||
guard abs >= 1000 else {return "\(sign)\(abs)"}
|
||||
let exp = Int(log10(Double(abs)) / log10(1000))
|
||||
let units = ["K", "M", "G", "T", "P", "E"]
|
||||
let rounded = round(10 * Double(abs) / pow(1000.0,Double(exp))) / 10;
|
||||
let rounded = round(10 * Double(abs) / pow(1000.0, Double(exp))) / 10
|
||||
return "\(sign)\(rounded)\(units[exp-1])"
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ struct LibraryOperations {
|
||||
/// Open a zim file with url
|
||||
/// - Parameter url: url of the zim file
|
||||
@discardableResult
|
||||
static func open(url: URL) -> ZimFileMetaData? {
|
||||
static func open(url: URL, onComplete: (() -> Void)? = nil) -> ZimFileMetaData? {
|
||||
guard let metadata = ZimFileService.getMetaData(url: url),
|
||||
let fileURLBookmark = ZimFileService.getBookmarkData(url: url) else { return nil }
|
||||
|
||||
@ -45,18 +45,24 @@ struct LibraryOperations {
|
||||
zimFile.fileURLBookmark = fileURLBookmark
|
||||
zimFile.isMissing = false
|
||||
if context.hasChanges { try? context.save() }
|
||||
DispatchQueue.main.async {
|
||||
onComplete?()
|
||||
}
|
||||
}
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
||||
/// Reopen zim files from url bookmark data.
|
||||
static func reopen() {
|
||||
static func reopen(onComplete: (() -> Void)?) {
|
||||
var successCount = 0
|
||||
let context = Database.shared.container.viewContext
|
||||
let request = ZimFile.fetchRequest(predicate: ZimFile.withFileURLBookmarkPredicate)
|
||||
|
||||
guard let zimFiles = try? context.fetch(request) else { return }
|
||||
guard let zimFiles = try? context.fetch(request) else {
|
||||
onComplete?()
|
||||
return
|
||||
}
|
||||
zimFiles.forEach { zimFile in
|
||||
guard let data = zimFile.fileURLBookmark else { return }
|
||||
do {
|
||||
@ -77,6 +83,7 @@ struct LibraryOperations {
|
||||
}
|
||||
|
||||
os_log("Reopened %d out of %d zim files", log: Log.LibraryOperations, type: .info, successCount, zimFiles.count)
|
||||
onComplete?()
|
||||
}
|
||||
|
||||
/// Scan a directory and open available zim files inside it
|
||||
|
@ -13,6 +13,7 @@ import WebKit
|
||||
import Defaults
|
||||
|
||||
import OrderedCollections
|
||||
import CoreKiwix
|
||||
|
||||
// swiftlint:disable file_length
|
||||
// swiftlint:disable:next type_body_length
|
||||
@ -46,7 +47,17 @@ final class BrowserViewModel: NSObject, ObservableObject,
|
||||
@Published private(set) var articleBookmarked = false
|
||||
@Published private(set) var outlineItems = [OutlineItem]()
|
||||
@Published private(set) var outlineItemTree = [OutlineItem]()
|
||||
@Published private(set) var url: URL?
|
||||
@Published private(set) var url: URL? {
|
||||
didSet {
|
||||
if !FeatureFlags.hasLibrary, url == nil {
|
||||
loadMainArticle()
|
||||
}
|
||||
if url != oldValue {
|
||||
bookmarkFetchedResultsController.fetchRequest.predicate = Self.bookmarksPredicateFor(url: url)
|
||||
try? bookmarkFetchedResultsController.performFetch()
|
||||
}
|
||||
}
|
||||
}
|
||||
@Published var externalURL: URL?
|
||||
|
||||
private(set) var tabID: NSManagedObjectID? {
|
||||
@ -67,18 +78,26 @@ final class BrowserViewModel: NSObject, ObservableObject,
|
||||
private var canGoBackObserver: NSKeyValueObservation?
|
||||
private var canGoForwardObserver: NSKeyValueObservation?
|
||||
private var titleURLObserver: AnyCancellable?
|
||||
private var bookmarkFetchedResultsController: NSFetchedResultsController<Bookmark>?
|
||||
private let bookmarkFetchedResultsController: NSFetchedResultsController<Bookmark>
|
||||
/// A temporary placeholder for the url that should be opened in a new tab, set on macOS only
|
||||
static var urlForNewTab: URL?
|
||||
private var cancellables: Set<AnyCancellable> = []
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
init(tabID: NSManagedObjectID? = nil) {
|
||||
self.tabID = tabID
|
||||
webView = WKWebView(frame: .zero, configuration: WebViewConfiguration())
|
||||
// Bookmark fetching:
|
||||
bookmarkFetchedResultsController = NSFetchedResultsController(
|
||||
fetchRequest: Bookmark.fetchRequest(), // initially empty
|
||||
managedObjectContext: Database.viewContext,
|
||||
sectionNameKeyPath: nil,
|
||||
cacheName: nil
|
||||
)
|
||||
super.init()
|
||||
|
||||
bookmarkFetchedResultsController.delegate = self
|
||||
|
||||
// configure web view
|
||||
webView.allowsBackForwardNavigationGestures = true
|
||||
webView.configuration.defaultWebpagePreferences.preferredContentMode = .mobile // for font adjustment to work
|
||||
@ -116,7 +135,6 @@ final class BrowserViewModel: NSObject, ObservableObject,
|
||||
guard let title, let url else { return }
|
||||
self?.didUpdate(title: title, url: url)
|
||||
}
|
||||
bookmarkFetchedResultsController?.delegate = self
|
||||
}
|
||||
|
||||
private func didUpdate(title: String, url: URL) {
|
||||
@ -138,17 +156,6 @@ final class BrowserViewModel: NSObject, ObservableObject,
|
||||
tab.title = title
|
||||
tab.zimFile = zimFile
|
||||
}
|
||||
|
||||
// setup bookmark fetched results controller
|
||||
bookmarkFetchedResultsController = NSFetchedResultsController(
|
||||
fetchRequest: Bookmark.fetchRequest(predicate: {
|
||||
return NSPredicate(format: "articleURL == %@", url as CVarArg)
|
||||
}()),
|
||||
managedObjectContext: Database.viewContext,
|
||||
sectionNameKeyPath: nil,
|
||||
cacheName: nil
|
||||
)
|
||||
try? bookmarkFetchedResultsController?.performFetch()
|
||||
}
|
||||
|
||||
func updateLastOpened() {
|
||||
@ -170,6 +177,7 @@ final class BrowserViewModel: NSObject, ObservableObject,
|
||||
func load(url: URL) {
|
||||
guard webView.url != url else { return }
|
||||
webView.load(URLRequest(url: url))
|
||||
self.url = url
|
||||
}
|
||||
|
||||
func loadRandomArticle(zimFileID: UUID? = nil) {
|
||||
@ -312,9 +320,10 @@ final class BrowserViewModel: NSObject, ObservableObject,
|
||||
let webView = WKWebView(frame: .zero, configuration: WebViewConfiguration())
|
||||
webView.load(URLRequest(url: url))
|
||||
return WebViewController(webView: webView)
|
||||
}, actionProvider: { _ in
|
||||
},
|
||||
actionProvider: { _ in
|
||||
var actions = [UIAction]()
|
||||
|
||||
|
||||
// open url
|
||||
actions.append(
|
||||
UIAction(title: "Open".localized, image: UIImage(systemName: "doc.text")) { _ in
|
||||
@ -326,19 +335,23 @@ final class BrowserViewModel: NSObject, ObservableObject,
|
||||
NotificationCenter.openURL(url, inNewTab: true)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
// bookmark
|
||||
let bookmarkAction: UIAction = {
|
||||
let context = Database.viewContext
|
||||
let predicate = NSPredicate(format: "articleURL == %@", url as CVarArg)
|
||||
let request = Bookmark.fetchRequest(predicate: predicate)
|
||||
if let bookmarks = try? context.fetch(request), !bookmarks.isEmpty {
|
||||
if let bookmarks = try? context.fetch(request),
|
||||
!bookmarks.isEmpty {
|
||||
return UIAction(title: "Remove Bookmark".localized,
|
||||
image: UIImage(systemName: "star.slash.fill")) { [weak self] _ in
|
||||
self?.deleteBookmark(url: url)
|
||||
}
|
||||
} else {
|
||||
return UIAction(title: "Bookmark".localized, image: UIImage(systemName: "star")) { [weak self] _ in
|
||||
return UIAction(
|
||||
title: "Bookmark".localized,
|
||||
image: UIImage(systemName: "star")
|
||||
) { [weak self] _ in
|
||||
self?.createBookmark(url: url)
|
||||
}
|
||||
}
|
||||
@ -531,4 +544,9 @@ final class BrowserViewModel: NSObject, ObservableObject,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func bookmarksPredicateFor(url: URL?) -> NSPredicate? {
|
||||
guard let url else { return nil }
|
||||
return NSPredicate(format: "articleURL == %@", url as CVarArg)
|
||||
}
|
||||
}
|
||||
|
@ -11,17 +11,9 @@ import WebKit
|
||||
|
||||
@MainActor
|
||||
class NavigationViewModel: ObservableObject {
|
||||
@Published var currentItem: NavigationItem?
|
||||
@Published var readingURL: URL?
|
||||
// remained optional due to focusedSceneValue conformance
|
||||
@Published var currentItem: NavigationItem? = .loading
|
||||
|
||||
init() {
|
||||
#if os(macOS)
|
||||
currentItem = .reading
|
||||
#elseif os(iOS)
|
||||
navigateToMostRecentTab()
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: - Tab Management
|
||||
|
||||
private func makeTab(context: NSManagedObjectContext) -> Tab {
|
||||
@ -55,11 +47,9 @@ class NavigationViewModel: ObservableObject {
|
||||
|
||||
@MainActor
|
||||
func tabIDFor(url: URL?) -> NSManagedObjectID {
|
||||
guard let url else {
|
||||
return createTab()
|
||||
}
|
||||
let coordinator = Database.viewContext.persistentStoreCoordinator
|
||||
guard let tabID = coordinator?.managedObjectID(forURIRepresentation: url) else {
|
||||
guard let url,
|
||||
let coordinator = Database.viewContext.persistentStoreCoordinator,
|
||||
let tabID = coordinator.managedObjectID(forURIRepresentation: url) else {
|
||||
return createTab()
|
||||
}
|
||||
return tabID
|
||||
|
@ -42,7 +42,7 @@ struct BrowserTab: View {
|
||||
.searchable(text: $search.searchText, placement: .toolbar)
|
||||
.modify { view in
|
||||
#if os(macOS)
|
||||
view.navigationTitle(browser.articleTitle.isEmpty ? "Kiwix" : browser.articleTitle)
|
||||
view.navigationTitle(browser.articleTitle.isEmpty ? Brand.appName : browser.articleTitle)
|
||||
.navigationSubtitle(browser.zimFileName)
|
||||
#elseif os(iOS)
|
||||
view
|
||||
@ -59,7 +59,7 @@ struct BrowserTab: View {
|
||||
struct Content: View {
|
||||
@Environment(\.isSearching) private var isSearching
|
||||
@EnvironmentObject private var browser: BrowserViewModel
|
||||
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { proxy in
|
||||
Group {
|
||||
@ -70,7 +70,7 @@ struct BrowserTab: View {
|
||||
#elseif os(iOS)
|
||||
.environment(\.horizontalSizeClass, proxy.size.width > 750 ? .regular : .compact)
|
||||
#endif
|
||||
} else if browser.url == nil {
|
||||
} else if browser.url == nil && FeatureFlags.hasLibrary {
|
||||
Welcome()
|
||||
} else {
|
||||
WebView().ignoresSafeArea()
|
||||
|
13
Views/BuildingBlocks/LoadingView.swift
Normal file
13
Views/BuildingBlocks/LoadingView.swift
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright © 2023 Kiwix.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct LoadingView: View {
|
||||
var body: some View {
|
||||
Text("Loading...".localized)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
LoadingView()
|
||||
}
|
@ -11,7 +11,7 @@ import SwiftUI
|
||||
struct BookmarkButton: View {
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
@EnvironmentObject private var browser: BrowserViewModel
|
||||
@State private var isShowingBookmark = false
|
||||
@State private var isShowingPopOver = false
|
||||
|
||||
var body: some View {
|
||||
#if os(macOS)
|
||||
@ -45,7 +45,7 @@ struct BookmarkButton: View {
|
||||
}
|
||||
}
|
||||
Button {
|
||||
isShowingBookmark = true
|
||||
isShowingPopOver = true
|
||||
} label: {
|
||||
Label("Show Bookmarks".localized, systemImage: "list.star")
|
||||
}
|
||||
@ -57,15 +57,15 @@ struct BookmarkButton: View {
|
||||
.renderingMode(browser.articleBookmarked ? .original : .template)
|
||||
}
|
||||
} primaryAction: {
|
||||
isShowingBookmark = true
|
||||
isShowingPopOver = true
|
||||
}
|
||||
.help("Show bookmarks. Long press to bookmark or unbookmark the current article.".localized)
|
||||
.popover(isPresented: $isShowingBookmark) {
|
||||
.popover(isPresented: $isShowingPopOver) {
|
||||
NavigationView {
|
||||
Bookmarks().navigationBarTitleDisplayMode(.inline).toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button {
|
||||
isShowingBookmark = false
|
||||
isShowingPopOver = false
|
||||
} label: {
|
||||
Text("Done".localized).fontWeight(.semibold)
|
||||
}
|
||||
|
@ -51,10 +51,12 @@ struct TabsManagerButton: View {
|
||||
}
|
||||
}
|
||||
Section {
|
||||
Button {
|
||||
presentedSheet = .library
|
||||
} label: {
|
||||
Label("Library".localized, systemImage: "folder")
|
||||
if FeatureFlags.hasLibrary {
|
||||
Button {
|
||||
presentedSheet = .library
|
||||
} label: {
|
||||
Label("Library".localized, systemImage: "folder")
|
||||
}
|
||||
}
|
||||
Button {
|
||||
presentedSheet = .settings
|
||||
|
@ -86,8 +86,10 @@ struct SidebarNavigationCommands: View {
|
||||
|
||||
var body: some View {
|
||||
buildButtons([.reading, .bookmarks], modifiers: [.command])
|
||||
Divider()
|
||||
buildButtons([.opened, .categories, .downloads, .new], modifiers: [.command, .control])
|
||||
if FeatureFlags.hasLibrary {
|
||||
Divider()
|
||||
buildButtons([.opened, .categories, .downloads, .new], modifiers: [.command, .control])
|
||||
}
|
||||
}
|
||||
|
||||
private func buildButtons(_ navigationItems: [NavigationItem], modifiers: EventModifiers = []) -> some View {
|
||||
|
@ -108,19 +108,21 @@ struct SearchResults: View {
|
||||
}
|
||||
} header: { recentSearchHeader }
|
||||
}
|
||||
Section {
|
||||
ForEach(zimFiles) { zimFile in
|
||||
HStack {
|
||||
Toggle(zimFile.name, isOn: Binding<Bool>(get: {
|
||||
zimFile.includedInSearch
|
||||
}, set: {
|
||||
zimFile.includedInSearch = $0
|
||||
try? managedObjectContext.save()
|
||||
}))
|
||||
Spacer()
|
||||
if FeatureFlags.hasLibrary {
|
||||
Section {
|
||||
ForEach(zimFiles) { zimFile in
|
||||
HStack {
|
||||
Toggle(zimFile.name, isOn: Binding<Bool>(get: {
|
||||
zimFile.includedInSearch
|
||||
}, set: {
|
||||
zimFile.includedInSearch = $0
|
||||
try? managedObjectContext.save()
|
||||
}))
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
} header: { searchFilterHeader }
|
||||
} header: { searchFilterHeader }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,19 +25,23 @@ struct ReadingSettings: View {
|
||||
Button("Reset".localized) { webViewPageZoom = 1 }.disabled(webViewPageZoom == 1)
|
||||
}
|
||||
}
|
||||
SettingSection(name: "External link".localized) {
|
||||
Picker(selection: $externalLinkLoadingPolicy) {
|
||||
ForEach(ExternalLinkLoadingPolicy.allCases) { loadingPolicy in
|
||||
Text(loadingPolicy.name.localized).tag(loadingPolicy)
|
||||
}
|
||||
} label: { }
|
||||
if FeatureFlags.showExternalLinkOptionInSettings {
|
||||
SettingSection(name: "External link".localized) {
|
||||
Picker(selection: $externalLinkLoadingPolicy) {
|
||||
ForEach(ExternalLinkLoadingPolicy.allCases) { loadingPolicy in
|
||||
Text(loadingPolicy.name.localized).tag(loadingPolicy)
|
||||
}
|
||||
} label: { }
|
||||
}
|
||||
}
|
||||
SettingSection(name: "Search snippet".localized) {
|
||||
Picker(selection: $searchResultSnippetMode) {
|
||||
ForEach(SearchResultSnippetMode.allCases) { snippetMode in
|
||||
Text(snippetMode.name.localized).tag(snippetMode)
|
||||
}
|
||||
} label: { }
|
||||
if FeatureFlags.showSearchSnippetInSettings {
|
||||
SettingSection(name: "Search snippet".localized) {
|
||||
Picker(selection: $searchResultSnippetMode) {
|
||||
ForEach(SearchResultSnippetMode.allCases) { snippetMode in
|
||||
Text(snippetMode.name.localized).tag(snippetMode)
|
||||
}
|
||||
} label: { }
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
@ -118,15 +122,24 @@ struct Settings: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
readingSettings
|
||||
librarySettings
|
||||
catalogSettings
|
||||
backupSettings
|
||||
miscellaneous
|
||||
if FeatureFlags.hasLibrary {
|
||||
List {
|
||||
readingSettings
|
||||
librarySettings
|
||||
catalogSettings
|
||||
backupSettings
|
||||
miscellaneous
|
||||
}
|
||||
.modifier(ToolbarRoleBrowser())
|
||||
.navigationTitle("Settings".localized)
|
||||
} else {
|
||||
List {
|
||||
readingSettings
|
||||
miscellaneous
|
||||
}
|
||||
.modifier(ToolbarRoleBrowser())
|
||||
.navigationTitle("Settings".localized)
|
||||
}
|
||||
.modifier(ToolbarRoleBrowser())
|
||||
.navigationTitle("Settings".localized)
|
||||
}
|
||||
|
||||
var readingSettings: some View {
|
||||
@ -135,14 +148,18 @@ struct Settings: View {
|
||||
Text("Page zoom".localized +
|
||||
": \(Formatter.percent.string(from: NSNumber(value: webViewPageZoom)) ?? "")")
|
||||
}
|
||||
Picker("External link".localized, selection: $externalLinkLoadingPolicy) {
|
||||
ForEach(ExternalLinkLoadingPolicy.allCases) { loadingPolicy in
|
||||
Text(loadingPolicy.name.localized).tag(loadingPolicy)
|
||||
if FeatureFlags.showExternalLinkOptionInSettings {
|
||||
Picker("External link".localized, selection: $externalLinkLoadingPolicy) {
|
||||
ForEach(ExternalLinkLoadingPolicy.allCases) { loadingPolicy in
|
||||
Text(loadingPolicy.name.localized).tag(loadingPolicy)
|
||||
}
|
||||
}
|
||||
}
|
||||
Picker("Search snippet".localized, selection: $searchResultSnippetMode) {
|
||||
ForEach(SearchResultSnippetMode.allCases) { snippetMode in
|
||||
Text(snippetMode.name.localized).tag(snippetMode)
|
||||
if FeatureFlags.showSearchSnippetInSettings {
|
||||
Picker("Search snippet".localized, selection: $searchResultSnippetMode) {
|
||||
ForEach(SearchResultSnippetMode.allCases) { snippetMode in
|
||||
Text(snippetMode.name.localized).tag(snippetMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -203,7 +220,8 @@ struct Settings: View {
|
||||
Section("Misc".localized) {
|
||||
Button("Feedback".localized) { UIApplication.shared.open(URL(string: "mailto:feedback@kiwix.org")!) }
|
||||
Button("Rate the App".localized) {
|
||||
let url = URL(string: "itms-apps://itunes.apple.com/us/app/kiwix/id997079563?action=write-review")!
|
||||
let url = URL(appStoreReviewForName: Brand.appName.lowercased(),
|
||||
appStoreID: Brand.appStoreId)
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
NavigationLink("About".localized) { About() }
|
||||
|
@ -94,15 +94,15 @@ struct Welcome: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Kiwix logo shown in onboarding view
|
||||
/// App logo shown in onboarding view
|
||||
private var logo: some View {
|
||||
VStack(spacing: 6) {
|
||||
Image("Kiwix_logo_v3")
|
||||
Image(Brand.welcomeLogoImageName)
|
||||
.resizable()
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.frame(width: 60, height: 60)
|
||||
.background(RoundedRectangle(cornerRadius: 10, style: .continuous).foregroundColor(.white))
|
||||
Text("KIWIX").font(.largeTitle).fontWeight(.bold)
|
||||
Text(Brand.appName.uppercased()).font(.largeTitle).fontWeight(.bold)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user