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