diff --git a/App/App_iOS.swift b/App/App_iOS.swift index 6754ee8c..2798d3f2 100644 --- a/App/App_iOS.swift +++ b/App/App_iOS.swift @@ -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) { diff --git a/App/App_macOS.swift b/App/App_macOS.swift index d2f33635..589a8b79 100644 --- a/App/App_macOS.swift +++ b/App/App_macOS.swift @@ -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 + } + } } } } diff --git a/App/CompactViewController.swift b/App/CompactViewController.swift index 0aabeae7..69a84eee 100644 --- a/App/CompactViewController.swift +++ b/App/CompactViewController.swift @@ -44,6 +44,7 @@ class CompactViewController: UIHostingController, 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, 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, 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( diff --git a/App/SidebarViewController.swift b/App/SidebarViewController.swift index a29afb15..a2f7a743 100644 --- a/App/SidebarViewController.swift +++ b/App/SidebarViewController.swift @@ -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() - 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() diff --git a/App/SplitViewController.swift b/App/SplitViewController.swift index 45a733c4..02600762 100644 --- a/App/SplitViewController.swift +++ b/App/SplitViewController.swift @@ -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) diff --git a/Kiwix.xcodeproj/project.pbxproj b/Kiwix.xcodeproj/project.pbxproj index d18989bb..5ff5e393 100644 --- a/Kiwix.xcodeproj/project.pbxproj +++ b/Kiwix.xcodeproj/project.pbxproj @@ -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 = ""; }; 97DE2BA4283A944100C63D9B /* GridCommon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridCommon.swift; sourceTree = ""; }; 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 = ""; }; 97E94B22271EF250005B0295 /* Kiwix.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Kiwix.entitlements; sourceTree = ""; }; 97F3332E28AFC1A2007FF53C /* SearchResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResults.swift; sourceTree = ""; }; 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 = ""; }; 97FD2F5E251EA07B0034927C /* FeatureFlags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlags.swift; sourceTree = ""; }; + 980179502B0DF42100E8E1E2 /* Brand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Brand.swift; sourceTree = ""; }; + 980179532B0E402C00E8E1E2 /* kiwix.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = kiwix.xcconfig; sourceTree = ""; }; + 983A3F552B129F6B000E7A51 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 983D22662B16ABB6005EBAF1 /* LoadingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -317,6 +323,7 @@ 974E7EE22930201500BDF59C /* ZimFileService */, 9735D0B82775363900C7D495 /* DataModel.xcdatamodeld */, 973A0DE5281DC8F400B41E71 /* DownloadService.swift */, + 980179502B0DF42100E8E1E2 /* Brand.swift */, ); path = Model; sourceTree = ""; @@ -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 = ""; @@ -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; diff --git a/Model/Brand.swift b/Model/Brand.swift new file mode 100644 index 00000000..bc517827 --- /dev/null +++ b/Model/Brand.swift @@ -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(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 + } + } +} diff --git a/Model/Utilities/FeatureFlags.swift b/Model/Utilities/FeatureFlags.swift index 7fcaeb4f..71a2f702 100644 --- a/Model/Utilities/FeatureFlags.swift +++ b/Model/Utilities/FeatureFlags.swift @@ -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 } diff --git a/Model/Utilities/Log.swift b/Model/Utilities/Log.swift index 0817aa6b..49e76dc9 100644 --- a/Model/Utilities/Log.swift +++ b/Model/Utilities/Log.swift @@ -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") } diff --git a/Model/Utilities/URL.swift b/Model/Utilities/URL.swift index a39baa35..1c033768 100644 --- a/Model/Utilities/URL.swift +++ b/Model/Utilities/URL.swift @@ -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")! + } } diff --git a/Support/Assets.xcassets/Kiwix_logo_v3.imageset/Contents.json b/Support/Assets.xcassets/welcomeLogo.imageset/Contents.json similarity index 100% rename from Support/Assets.xcassets/Kiwix_logo_v3.imageset/Contents.json rename to Support/Assets.xcassets/welcomeLogo.imageset/Contents.json diff --git a/Support/Assets.xcassets/Kiwix_logo_v3.imageset/Kiwix_logo_v3.svg b/Support/Assets.xcassets/welcomeLogo.imageset/Kiwix_logo_v3.svg similarity index 100% rename from Support/Assets.xcassets/Kiwix_logo_v3.imageset/Kiwix_logo_v3.svg rename to Support/Assets.xcassets/welcomeLogo.imageset/Kiwix_logo_v3.svg diff --git a/Support/Info.plist b/Support/Info.plist index 00e31bf1..b0f83770 100644 --- a/Support/Info.plist +++ b/Support/Info.plist @@ -2,8 +2,8 @@ - - + APP_STORE_ID + $(APP_STORE_ID) BGTaskSchedulerPermittedIdentifiers org.kiwix.library_refresh diff --git a/Support/en.lproj/Localizable.strings b/Support/en.lproj/Localizable.strings index b386eb7f..a245603a 100644 --- a/Support/en.lproj/Localizable.strings +++ b/Support/en.lproj/Localizable.strings @@ -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..."; \ No newline at end of file diff --git a/Support/kiwix.xcconfig b/Support/kiwix.xcconfig new file mode 100644 index 00000000..a3b50bb2 --- /dev/null +++ b/Support/kiwix.xcconfig @@ -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 diff --git a/SwiftUI/Model/DefaultKeys.swift b/SwiftUI/Model/DefaultKeys.swift index fc1e4e46..5f1843c1 100644 --- a/SwiftUI/Model/DefaultKeys.swift +++ b/SwiftUI/Model/DefaultKeys.swift @@ -13,10 +13,10 @@ extension Defaults.Keys { static let webViewTextSizeAdjustFactor = Key("webViewZoomScale", default: 1) static let webViewPageZoom = Key("webViewPageZoom", default: 1) static let externalLinkLoadingPolicy = Key( - "externalLinkLoadingPolicy", default: .alwaysAsk + "externalLinkLoadingPolicy", default: Brand.defaultExternalLinkPolicy ) static let searchResultSnippetMode = Key( - "searchResultSnippetMode", default: .firstSentence + "searchResultSnippetMode", default: Brand.defaultSearchSnippetMode ) // // // UI diff --git a/SwiftUI/Model/DirectoryMonitor.swift b/SwiftUI/Model/DirectoryMonitor.swift index 756a4902..1b0284c0 100644 --- a/SwiftUI/Model/DirectoryMonitor.swift +++ b/SwiftUI/Model/DirectoryMonitor.swift @@ -24,7 +24,6 @@ class DirectoryMonitor { init(url: URL, onChange: ((URL) -> Void)? = nil) { self.url = url self.onChange = onChange - start() } // MARK: Monitoring diff --git a/SwiftUI/Model/Enum.swift b/SwiftUI/Model/Enum.swift index f6561af8..aa963d29 100644 --- a/SwiftUI/Model/Enum.swift +++ b/SwiftUI/Model/Enum.swift @@ -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 diff --git a/SwiftUI/Model/Formatter.swift b/SwiftUI/Model/Formatter.swift index 162042e5..e519d54a 100644 --- a/SwiftUI/Model/Formatter.swift +++ b/SwiftUI/Model/Formatter.swift @@ -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])" } } diff --git a/SwiftUI/Model/LibraryOperations.swift b/SwiftUI/Model/LibraryOperations.swift index 748a5daf..8c9280a0 100644 --- a/SwiftUI/Model/LibraryOperations.swift +++ b/SwiftUI/Model/LibraryOperations.swift @@ -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 diff --git a/ViewModel/BrowserViewModel.swift b/ViewModel/BrowserViewModel.swift index 5716da1b..13ca2448 100644 --- a/ViewModel/BrowserViewModel.swift +++ b/ViewModel/BrowserViewModel.swift @@ -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? + private let bookmarkFetchedResultsController: NSFetchedResultsController /// 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 = [] // 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) + } } diff --git a/ViewModel/NavigationViewModel.swift b/ViewModel/NavigationViewModel.swift index 3538ab65..1ab6a302 100644 --- a/ViewModel/NavigationViewModel.swift +++ b/ViewModel/NavigationViewModel.swift @@ -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 diff --git a/Views/BrowserTab.swift b/Views/BrowserTab.swift index c3cfe941..00014b42 100644 --- a/Views/BrowserTab.swift +++ b/Views/BrowserTab.swift @@ -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() diff --git a/Views/BuildingBlocks/LoadingView.swift b/Views/BuildingBlocks/LoadingView.swift new file mode 100644 index 00000000..b7f7185d --- /dev/null +++ b/Views/BuildingBlocks/LoadingView.swift @@ -0,0 +1,13 @@ +// Copyright © 2023 Kiwix. + +import SwiftUI + +struct LoadingView: View { + var body: some View { + Text("Loading...".localized) + } +} + +#Preview { + LoadingView() +} diff --git a/Views/Buttons/BookmarkButton.swift b/Views/Buttons/BookmarkButton.swift index 96b40565..fa5ad3a2 100644 --- a/Views/Buttons/BookmarkButton.swift +++ b/Views/Buttons/BookmarkButton.swift @@ -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) } diff --git a/Views/Buttons/TabsManagerButton.swift b/Views/Buttons/TabsManagerButton.swift index 212ce302..ec36e5e5 100644 --- a/Views/Buttons/TabsManagerButton.swift +++ b/Views/Buttons/TabsManagerButton.swift @@ -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 diff --git a/Views/Commands.swift b/Views/Commands.swift index 61672c3d..967aefe2 100644 --- a/Views/Commands.swift +++ b/Views/Commands.swift @@ -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 { diff --git a/Views/SearchResults.swift b/Views/SearchResults.swift index a617a1fe..d0607d1b 100644 --- a/Views/SearchResults.swift +++ b/Views/SearchResults.swift @@ -108,19 +108,21 @@ struct SearchResults: View { } } header: { recentSearchHeader } } - Section { - ForEach(zimFiles) { zimFile in - HStack { - Toggle(zimFile.name, isOn: Binding(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(get: { + zimFile.includedInSearch + }, set: { + zimFile.includedInSearch = $0 + try? managedObjectContext.save() + })) + Spacer() + } } - } - } header: { searchFilterHeader } + } header: { searchFilterHeader } + } } } diff --git a/Views/Settings/Settings.swift b/Views/Settings/Settings.swift index 2478da1d..71eb27a8 100644 --- a/Views/Settings/Settings.swift +++ b/Views/Settings/Settings.swift @@ -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() } diff --git a/Views/Welcome.swift b/Views/Welcome.swift index 377c58e6..76c17a1f 100644 --- a/Views/Welcome.swift +++ b/Views/Welcome.swift @@ -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) } }