From 38217e43e41aa485c462819ee4ff5c0cc9a2d98a Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 21 Jan 2022 08:51:50 +0800 Subject: [PATCH 01/22] initial 6.2 commit - Switches to `UTType` for ShareViewController --- NetNewsWire.xcodeproj/project.pbxproj | 8 ++++++++ iOS/ShareExtension/ShareViewController.swift | 9 +++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index ff66384e0..c533942ad 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -4628,6 +4628,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 1768140A2564BB8300D98635 /* NetNewsWire_iOSwidgetextension_target.xcconfig */; buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 15.0; }; name = Debug; }; @@ -4635,6 +4636,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 1768140A2564BB8300D98635 /* NetNewsWire_iOSwidgetextension_target.xcconfig */; buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 15.0; }; name = Release; }; @@ -4656,6 +4658,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 51314617235A797400387FDC /* NetNewsWire_iOSintentextension_target.xcconfig */; buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 15.0; }; name = Debug; }; @@ -4663,6 +4666,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 51314617235A797400387FDC /* NetNewsWire_iOSintentextension_target.xcconfig */; buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 15.0; }; name = Release; }; @@ -4670,6 +4674,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 515D4FCE2325B3D000EE1167 /* NetNewsWire_iOSshareextension_target.xcconfig */; buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 15.0; }; name = Debug; }; @@ -4677,6 +4682,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 515D4FCE2325B3D000EE1167 /* NetNewsWire_iOSshareextension_target.xcconfig */; buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 15.0; }; name = Release; }; @@ -4754,6 +4760,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */; buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 15.0; }; name = Debug; }; @@ -4761,6 +4768,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */; buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 15.0; }; name = Release; }; diff --git a/iOS/ShareExtension/ShareViewController.swift b/iOS/ShareExtension/ShareViewController.swift index 9da5495db..3a3f7178f 100644 --- a/iOS/ShareExtension/ShareViewController.swift +++ b/iOS/ShareExtension/ShareViewController.swift @@ -12,6 +12,7 @@ import Account import Social import RSCore import RSTree +import UniformTypeIdentifiers class ShareViewController: SLComposeServiceViewController, ShareFolderPickerControllerDelegate { @@ -46,14 +47,14 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont // Try to get any HTML that is maybe passed in for item in self.extensionContext!.inputItems as! [NSExtensionItem] { for itemProvider in item.attachments! { - if itemProvider.hasItemConformingToTypeIdentifier(kUTTypePropertyList as String) { + if itemProvider.hasItemConformingToTypeIdentifier(UTType.propertyList.identifier) { provider = itemProvider } } } if provider != nil { - provider!.loadItem(forTypeIdentifier: kUTTypePropertyList as String, options: nil, completionHandler: { [weak self] (pList, error) in + provider!.loadItem(forTypeIdentifier: UTType.propertyList.identifier, options: nil, completionHandler: { [weak self] (pList, error) in if error != nil { return } @@ -73,14 +74,14 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont // Try to get the URL if it is passed in for item in self.extensionContext!.inputItems as! [NSExtensionItem] { for itemProvider in item.attachments! { - if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) { + if itemProvider.hasItemConformingToTypeIdentifier(UTType.url.identifier as String) { provider = itemProvider } } } if provider != nil { - provider!.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil, completionHandler: { [weak self] (urlCoded, error) in + provider!.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil, completionHandler: { [weak self] (urlCoded, error) in if error != nil { return } From 04f771975c38833975c7bef499831e79174292ae Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 21 Jan 2022 08:53:06 +0800 Subject: [PATCH 02/22] UserNotificationManager Removes deprecated `summaryArgument` and `summaryArgumentCount` from notification content. --- Shared/UserNotifications/UserNotificationManager.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Shared/UserNotifications/UserNotificationManager.swift b/Shared/UserNotifications/UserNotificationManager.swift index d8ed11eeb..7707b3c2f 100644 --- a/Shared/UserNotifications/UserNotificationManager.swift +++ b/Shared/UserNotifications/UserNotificationManager.swift @@ -53,8 +53,6 @@ private extension UserNotificationManager { } content.body = ArticleStringFormatter.truncatedSummary(article) content.threadIdentifier = webFeed.webFeedID - content.summaryArgument = "\(webFeed.nameForDisplay)" - content.summaryArgumentCount = 1 content.sound = UNNotificationSound.default content.userInfo = [UserInfoKey.articlePath: article.pathUserInfo] content.categoryIdentifier = "NEW_ARTICLE_NOTIFICATION_CATEGORY" From 4c734d9ba37515553b4118319bd3b2b37af57af1 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 21 Jan 2022 08:54:15 +0800 Subject: [PATCH 03/22] AppDelegate Updates notification presentation from `.alert` (deprecated) to `.banner`. --- iOS/AppDelegate.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift index f7cbe5b84..e8cbd8832 100644 --- a/iOS/AppDelegate.swift +++ b/iOS/AppDelegate.swift @@ -194,7 +194,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { - completionHandler([.alert, .badge, .sound]) + completionHandler([.banner, .badge, .sound]) } func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { From fe73d95df889fcfb3e8eea3a26e43dd00caa1845 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 21 Jan 2022 08:59:23 +0800 Subject: [PATCH 04/22] Updates to FontDescriptors --- Shared/Extensions/NSAttributedString+NetNewsWire.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Shared/Extensions/NSAttributedString+NetNewsWire.swift b/Shared/Extensions/NSAttributedString+NetNewsWire.swift index 98fb694b7..bdec2e9f0 100644 --- a/Shared/Extensions/NSAttributedString+NetNewsWire.swift +++ b/Shared/Extensions/NSAttributedString+NetNewsWire.swift @@ -224,7 +224,7 @@ extension NSAttributedString { #if canImport(AppKit) let features: [FontDescriptor.FeatureKey: Any] = [.typeIdentifier: kVerticalPositionType, .selectorIdentifier: forSuperscript ? kSuperiorsSelector : kInferiorsSelector] #else - let features: [FontDescriptor.FeatureKey: Any] = [.featureIdentifier: kVerticalPositionType, .typeIdentifier: forSuperscript ? kSuperiorsSelector : kInferiorsSelector] + let features: [FontDescriptor.FeatureKey: Any] = [.type: kVerticalPositionType, .selector: forSuperscript ? kSuperiorsSelector : kInferiorsSelector] #endif return features } From 52931c215a8bab572cd2c50ec6d122166c583059 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 21 Jan 2022 12:34:34 +0800 Subject: [PATCH 05/22] UniformTypeIdentifier update to FaviconDownloader --- Shared/Favicons/FaviconDownloader.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Shared/Favicons/FaviconDownloader.swift b/Shared/Favicons/FaviconDownloader.swift index bda684e43..02c48f6f0 100644 --- a/Shared/Favicons/FaviconDownloader.swift +++ b/Shared/Favicons/FaviconDownloader.swift @@ -11,6 +11,7 @@ import CoreServices import Articles import Account import RSCore +import UniformTypeIdentifiers extension Notification.Name { @@ -61,7 +62,7 @@ final class FaviconDownloader { loadHomePageToFaviconURLCache() loadHomePageURLsWithNoFaviconURLCache() - FaviconURLFinder.ignoredTypes = [kUTTypeScalableVectorGraphics as String] + FaviconURLFinder.ignoredTypes = [UTType.svg.identifier] NotificationCenter.default.addObserver(self, selector: #selector(didLoadFavicon(_:)), name: .DidLoadFavicon, object: nil) } From 7cf5f685f3f67969c93745236070b6c30a100a2a Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 21 Jan 2022 12:42:59 +0800 Subject: [PATCH 06/22] FaviconURLFinder / UniformTypeIdentifiers --- Shared/Favicons/FaviconURLFinder.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Shared/Favicons/FaviconURLFinder.swift b/Shared/Favicons/FaviconURLFinder.swift index dd51da4a5..b2198d520 100644 --- a/Shared/Favicons/FaviconURLFinder.swift +++ b/Shared/Favicons/FaviconURLFinder.swift @@ -9,6 +9,7 @@ import Foundation import CoreServices import RSParser +import UniformTypeIdentifiers // The favicon URLs may be specified in the head section of the home page. @@ -25,11 +26,11 @@ struct FaviconURLFinder { } for type in ignoredTypes { - if let mimeTypes = UTTypeCopyAllTagsWithClass(type as CFString, kUTTagClassMIMEType)?.takeRetainedValue() { - ignoredMimeTypes.append(contentsOf: mimeTypes as! [String]) + if let mimeType = UTTypeReference(type)?.preferredMIMEType { + ignoredMimeTypes.append(mimeType) } - if let extensions = UTTypeCopyAllTagsWithClass(type as CFString, kUTTagClassFilenameExtension)?.takeRetainedValue() { - ignoredExtensions.append(contentsOf: extensions as! [String]) + if let fileNameExtension = UTTypeReference(type)?.preferredFilenameExtension { + ignoredExtensions.append(fileNameExtension) } } } From b7775ab42365fdc83352ecb31fba7528ffb60a68 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 21 Jan 2022 12:44:19 +0800 Subject: [PATCH 07/22] ActivityManager / UniformTypeIdentifiers --- Shared/Activity/ActivityManager.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Shared/Activity/ActivityManager.swift b/Shared/Activity/ActivityManager.swift index 624bc4a30..b0c5d0784 100644 --- a/Shared/Activity/ActivityManager.swift +++ b/Shared/Activity/ActivityManager.swift @@ -13,6 +13,7 @@ import RSCore import Account import Articles import Intents +import UniformTypeIdentifiers class ActivityManager { @@ -218,7 +219,7 @@ private extension ActivityManager { #if os(iOS) func updateReadArticleSearchAttributes(with article: Article) { - let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeCompositeContent as String) + let attributeSet = CSSearchableItemAttributeSet(itemContentType: UTType.compositeContent.identifier) attributeSet.title = ArticleStringFormatter.truncatedTitle(article) attributeSet.contentDescription = article.summary attributeSet.keywords = makeKeywords(article) @@ -246,7 +247,7 @@ private extension ActivityManager { func updateSelectingActivityFeedSearchAttributes(with feed: WebFeed) { - let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String) + let attributeSet = CSSearchableItemAttributeSet(itemContentType: UTType.item.identifier) attributeSet.title = feed.nameForDisplay attributeSet.keywords = makeKeywords(feed.nameForDisplay) attributeSet.relatedUniqueIdentifier = ActivityManager.identifier(for: feed) @@ -265,7 +266,7 @@ private extension ActivityManager { // itself because the relatedUniqueIdentifier on the activity attributeset is populated. if let attributeSet = activity.contentAttributeSet { let identifier = attributeSet.relatedUniqueIdentifier - let tempAttributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String) + let tempAttributeSet = CSSearchableItemAttributeSet(itemContentType: UTType.item.identifier) let searchableItem = CSSearchableItem(uniqueIdentifier: identifier, domainIdentifier: nil, attributeSet: tempAttributeSet) CSSearchableIndex.default().indexSearchableItems([searchableItem]) } From 90fed800cbb5e0c20e8001d6b241fc3d69891ee5 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 21 Jan 2022 12:52:48 +0800 Subject: [PATCH 08/22] Adds `WKWebViewConfiguration` to PreloadedWebView --- iOS/Article/PreloadedWebView.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/iOS/Article/PreloadedWebView.swift b/iOS/Article/PreloadedWebView.swift index 3bf76a2ca..d2b38f9d1 100644 --- a/iOS/Article/PreloadedWebView.swift +++ b/iOS/Article/PreloadedWebView.swift @@ -17,9 +17,14 @@ class PreloadedWebView: WKWebView { init(articleIconSchemeHandler: ArticleIconSchemeHandler) { let preferences = WKPreferences() preferences.javaScriptCanOpenWindowsAutomatically = false - preferences.javaScriptEnabled = true + + /// The defaults for `preferredContentMode` and `allowsContentJavaScript` are suitable + /// and don't need to be explicity set. + /// `allowsContentJavaScript` replaces `WKPreferences.javascriptEnbaled`. + let webpagePreferences = WKWebpagePreferences() let configuration = WKWebViewConfiguration() + configuration.defaultWebpagePreferences = webpagePreferences configuration.preferences = preferences configuration.setValue(true, forKey: "allowUniversalAccessFromFileURLs") configuration.allowsInlineMediaPlayback = true From 6c9c3749bb0cd84ae0ec0c8e16f80939a7725b4c Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 21 Jan 2022 12:53:24 +0800 Subject: [PATCH 09/22] UniformTypeIdentifiers --- iOS/MasterFeed/MasterFeedViewController+Drag.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/iOS/MasterFeed/MasterFeedViewController+Drag.swift b/iOS/MasterFeed/MasterFeedViewController+Drag.swift index 9bf7a5010..9553afedf 100644 --- a/iOS/MasterFeed/MasterFeedViewController+Drag.swift +++ b/iOS/MasterFeed/MasterFeedViewController+Drag.swift @@ -9,6 +9,7 @@ import UIKit import MobileCoreServices import Account +import UniformTypeIdentifiers extension MasterFeedViewController: UITableViewDragDelegate { @@ -20,7 +21,7 @@ extension MasterFeedViewController: UITableViewDragDelegate { let data = webFeed.url.data(using: .utf8) let itemProvider = NSItemProvider() - itemProvider.registerDataRepresentation(forTypeIdentifier: kUTTypeURL as String, visibility: .ownProcess) { completion in + itemProvider.registerDataRepresentation(forTypeIdentifier: UTType.url.identifier, visibility: .ownProcess) { completion in completion(data, nil) return nil } From ca690859f7271847b3c9a03ec6f8cabfc32c5b18 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 21 Jan 2022 13:07:38 +0800 Subject: [PATCH 10/22] Article Theme / UniformTypeIdentifiers --- Shared/ArticleStyles/ArticleTheme.swift | 7 +++++++ iOS/Settings/ArticleThemesTableViewController.swift | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Shared/ArticleStyles/ArticleTheme.swift b/Shared/ArticleStyles/ArticleTheme.swift index e40bde46f..65ad4f3b7 100644 --- a/Shared/ArticleStyles/ArticleTheme.swift +++ b/Shared/ArticleStyles/ArticleTheme.swift @@ -7,6 +7,13 @@ // import Foundation +import UniformTypeIdentifiers + +public extension UTType { + static var nnwTheme: UTType { + UTType("com.ranchero.netnewswire.theme")! + } +} struct ArticleTheme: Equatable { diff --git a/iOS/Settings/ArticleThemesTableViewController.swift b/iOS/Settings/ArticleThemesTableViewController.swift index 26e28944f..1aa252c5e 100644 --- a/iOS/Settings/ArticleThemesTableViewController.swift +++ b/iOS/Settings/ArticleThemesTableViewController.swift @@ -7,6 +7,7 @@ // import Foundation +import UniformTypeIdentifiers import UIKit @@ -27,7 +28,7 @@ class ArticleThemesTableViewController: UITableViewController { } @objc func importTheme(_ sender: Any?) { - let docPicker = UIDocumentPickerViewController(documentTypes: ["com.ranchero.netnewswire.theme"], in: .import) + let docPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.nnwTheme], asCopy: true) docPicker.delegate = self docPicker.modalPresentationStyle = .formSheet self.present(docPicker, animated: true) From cf2da858173438de6d5ab0f2847f7a84bcb34ecb Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 21 Jan 2022 19:52:59 +0800 Subject: [PATCH 11/22] SettingsViewController / UniformTypeIdentifiers --- iOS/Settings/SettingsViewController.swift | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/iOS/Settings/SettingsViewController.swift b/iOS/Settings/SettingsViewController.swift index 69f4a9829..b3fd91e2a 100644 --- a/iOS/Settings/SettingsViewController.swift +++ b/iOS/Settings/SettingsViewController.swift @@ -11,6 +11,7 @@ import Account import CoreServices import SafariServices import SwiftUI +import UniformTypeIdentifiers class SettingsViewController: UITableViewController { @@ -451,16 +452,9 @@ private extension SettingsViewController { func importOPMLDocumentPicker() { - let utiArray = UTTypeCreateAllIdentifiersForTag(kUTTagClassFilenameExtension, "opml" as NSString, nil)?.takeRetainedValue() as? [String] ?? [String]() - - var opmlUTIs = utiArray - .compactMap({ UTTypeCopyDeclaration($0 as NSString)?.takeUnretainedValue() as? [String: Any] }) - .reduce([String]()) { (result, dict) in - return result + dict.values.compactMap({ $0 as? String }) - } - opmlUTIs.append("public.xml") + let utiArray = UTType.types(tag: "opml", tagClass: .filenameExtension, conformingTo: nil) - let docPicker = UIDocumentPickerViewController(documentTypes: opmlUTIs, in: .import) + let docPicker = UIDocumentPickerViewController(forOpeningContentTypes: utiArray, asCopy: true) docPicker.delegate = self docPicker.modalPresentationStyle = .formSheet self.present(docPicker, animated: true) @@ -511,7 +505,7 @@ private extension SettingsViewController { self.presentError(title: "OPML Export Error", message: error.localizedDescription) } - let docPicker = UIDocumentPickerViewController(url: tempFile, in: .exportToService) + let docPicker = UIDocumentPickerViewController(forExporting: [tempFile], asCopy: true) docPicker.modalPresentationStyle = .formSheet self.present(docPicker, animated: true) } From 3a95cd0c3aa1227cc58c61f5b1a517eb54a444c1 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 21 Jan 2022 20:20:28 +0800 Subject: [PATCH 12/22] display mode updates --- iOS/SceneCoordinator.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 0d9b3a6fb..75dc6da95 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -324,7 +324,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { func start(for size: CGSize) -> UIViewController { rootSplitViewController = RootSplitViewController() rootSplitViewController.coordinator = self - rootSplitViewController.preferredDisplayMode = .allVisible + rootSplitViewController.preferredDisplayMode = .oneBesideSecondary rootSplitViewController.viewControllers = [InteractiveNavigationController.template()] rootSplitViewController.delegate = self @@ -1299,7 +1299,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { } func toggleSidebar() { - rootSplitViewController.preferredDisplayMode = rootSplitViewController.displayMode == .allVisible ? .primaryHidden : .allVisible + rootSplitViewController.preferredDisplayMode = rootSplitViewController.displayMode == .oneBesideSecondary ? .secondaryOnly : .oneBesideSecondary } func selectArticleInCurrentFeed(_ articleID: String, isShowingExtractedArticle: Bool? = nil, articleWindowScrollY: Int? = nil) { From e2ef4bc5121e32b002308664bded89d8e4c8fc49 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Fri, 21 Jan 2022 20:39:48 +0800 Subject: [PATCH 13/22] SceneCoordinator preferredDisplayMode --- iOS/SceneCoordinator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 75dc6da95..e0a9873f4 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -2142,7 +2142,7 @@ private extension SceneCoordinator { rootSplitViewController.preferredPrimaryColumnWidthFraction = 0.30 subSplitViewController = UISplitViewController() - subSplitViewController!.preferredDisplayMode = .allVisible + subSplitViewController!.preferredDisplayMode = .oneBesideSecondary subSplitViewController!.viewControllers = [InteractiveNavigationController.template()] subSplitViewController!.preferredPrimaryColumnWidthFraction = 0.4285 From 39e5f36137da42e30d988efe1a73e028a024eb7f Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sat, 22 Jan 2022 06:43:22 +0800 Subject: [PATCH 14/22] Removes 1Password Fixes #3411 --- NetNewsWire.xcodeproj/project.pbxproj | 14 - Shared/1Password/OnePasswordExtension.h | 220 ------ Shared/1Password/OnePasswordExtension.m | 638 ------------------ .../FeedWranglerAccountViewController.swift | 15 - .../FeedbinAccountViewController.swift | 19 - .../NewsBlurAccountViewController.swift | 15 - .../ReaderAPIAccountViewController.swift | 29 - .../1password.imageset/Contents.json | 23 - .../1password.imageset/onepassword-navbar.png | Bin 770 -> 0 bytes .../onepassword-navbar@2x.png | Bin 1702 -> 0 bytes .../onepassword-navbar@3x.png | Bin 2757 -> 0 bytes .../NetNewsWire-iOS-Bridging-Header.h | 2 +- 12 files changed, 1 insertion(+), 974 deletions(-) delete mode 100644 Shared/1Password/OnePasswordExtension.h delete mode 100644 Shared/1Password/OnePasswordExtension.m delete mode 100644 iOS/Resources/Assets.xcassets/1password.imageset/Contents.json delete mode 100644 iOS/Resources/Assets.xcassets/1password.imageset/onepassword-navbar.png delete mode 100644 iOS/Resources/Assets.xcassets/1password.imageset/onepassword-navbar@2x.png delete mode 100644 iOS/Resources/Assets.xcassets/1password.imageset/onepassword-navbar@3x.png diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index c533942ad..368e8e991 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -54,7 +54,6 @@ 17D0682C2564F47E00C0B37E /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 17D0682B2564F47E00C0B37E /* Localizable.stringsdict */; }; 17D643B126F8A436008D4C05 /* ArticleThemeDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D643B026F8A436008D4C05 /* ArticleThemeDownloader.swift */; }; 17D643B226F8A436008D4C05 /* ArticleThemeDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D643B026F8A436008D4C05 /* ArticleThemeDownloader.swift */; }; - 17D7586F2679C21800B17787 /* OnePasswordExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 17D7586E2679C21800B17787 /* OnePasswordExtension.m */; }; 17E0084625941887000C23F0 /* SizeCategories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E0084525941887000C23F0 /* SizeCategories.swift */; }; 17EF6A2125C4E5B4002C9F81 /* RSWeb in Frameworks */ = {isa = PBXBuildFile; productRef = 17EF6A2025C4E5B4002C9F81 /* RSWeb */; }; 17EF6A2225C4E5B4002C9F81 /* RSWeb in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 17EF6A2025C4E5B4002C9F81 /* RSWeb */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; @@ -1149,8 +1148,6 @@ 17D0682B2564F47E00C0B37E /* Localizable.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = ""; }; 17D643B026F8A436008D4C05 /* ArticleThemeDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleThemeDownloader.swift; sourceTree = ""; }; 17D7586C2679C21700B17787 /* NetNewsWire-iOS-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NetNewsWire-iOS-Bridging-Header.h"; sourceTree = ""; }; - 17D7586D2679C21800B17787 /* OnePasswordExtension.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OnePasswordExtension.h; sourceTree = ""; }; - 17D7586E2679C21800B17787 /* OnePasswordExtension.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OnePasswordExtension.m; sourceTree = ""; }; 17E0084525941887000C23F0 /* SizeCategories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizeCategories.swift; sourceTree = ""; }; 3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedWranglerAccountViewController.swift; sourceTree = ""; }; 3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsFeedWrangler.xib; sourceTree = ""; }; @@ -1794,15 +1791,6 @@ path = Resources; sourceTree = ""; }; - 17D7586B2679C1DF00B17787 /* 1Password */ = { - isa = PBXGroup; - children = ( - 17D7586D2679C21800B17787 /* OnePasswordExtension.h */, - 17D7586E2679C21800B17787 /* OnePasswordExtension.m */, - ); - path = 1Password; - sourceTree = ""; - }; 510289CE2451BA1E00426DDF /* Twitter */ = { isa = PBXGroup; children = ( @@ -2594,7 +2582,6 @@ 510C43F5243D0325009F70C3 /* ExtensionPoints */, 511D43CE231FA51100FB1562 /* Resources */, 176813A22564B9D100D98635 /* Widget */, - 17D7586B2679C1DF00B17787 /* 1Password */, 173A64162547BE0900267F6E /* AccountType+Helpers.swift */, ); path = Shared; @@ -4190,7 +4177,6 @@ 8454C3F3263F2D8700E3F9C7 /* IconImageCache.swift in Sources */, B24E9ADE245AB88400DA5718 /* NSAttributedString+NetNewsWire.swift in Sources */, C5A6ED5223C9AF4300AB6BE2 /* TitleActivityItemSource.swift in Sources */, - 17D7586F2679C21800B17787 /* OnePasswordExtension.m in Sources */, 17071EF126F8137400F5E71D /* ArticleTheme+Notifications.swift in Sources */, 51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */, 84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */, diff --git a/Shared/1Password/OnePasswordExtension.h b/Shared/1Password/OnePasswordExtension.h deleted file mode 100644 index c8326d3ff..000000000 --- a/Shared/1Password/OnePasswordExtension.h +++ /dev/null @@ -1,220 +0,0 @@ -//Copyright (c) 2014-2020 AgileBits Inc. -// -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: -// -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. -// -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. - -#import -#import -#import - -#ifdef __IPHONE_8_0 -#import -#endif - -#if __has_feature(nullability) -NS_ASSUME_NONNULL_BEGIN -#else -#define nullable -#define __nullable -#define nonnull -#define __nonnull -#endif - -// Login Dictionary keys - Used to get or set the properties of a 1Password Login - -FOUNDATION_EXPORT NSString *const AppExtensionURLStringKey; -FOUNDATION_EXPORT NSString *const AppExtensionUsernameKey; -FOUNDATION_EXPORT NSString *const AppExtensionPasswordKey; -FOUNDATION_EXPORT NSString *const AppExtensionTOTPKey; -FOUNDATION_EXPORT NSString *const AppExtensionTitleKey; -FOUNDATION_EXPORT NSString *const AppExtensionNotesKey; -FOUNDATION_EXPORT NSString *const AppExtensionSectionTitleKey; -FOUNDATION_EXPORT NSString *const AppExtensionFieldsKey; -FOUNDATION_EXPORT NSString *const AppExtensionReturnedFieldsKey; -FOUNDATION_EXPORT NSString *const AppExtensionOldPasswordKey; -FOUNDATION_EXPORT NSString *const AppExtensionPasswordGeneratorOptionsKey; - -// Password Generator options - Used to set the 1Password Password Generator options when saving a new Login or when changing the password for for an existing Login -FOUNDATION_EXPORT NSString *const AppExtensionGeneratedPasswordMinLengthKey; -FOUNDATION_EXPORT NSString *const AppExtensionGeneratedPasswordMaxLengthKey; -FOUNDATION_EXPORT NSString *const AppExtensionGeneratedPasswordRequireDigitsKey; -FOUNDATION_EXPORT NSString *const AppExtensionGeneratedPasswordRequireSymbolsKey; -FOUNDATION_EXPORT NSString *const AppExtensionGeneratedPasswordForbiddenCharactersKey; - -// Errors codes -FOUNDATION_EXPORT NSString *const AppExtensionErrorDomain; - -FOUNDATION_EXPORT NS_ENUM(NSUInteger, AppExtensionErrorCode) { - AppExtensionErrorCodeCancelledByUser = 0, - AppExtensionErrorCodeAPINotAvailable = 1, - AppExtensionErrorCodeFailedToContactExtension = 2, - AppExtensionErrorCodeFailedToLoadItemProviderData = 3, - AppExtensionErrorCodeCollectFieldsScriptFailed = 4, - AppExtensionErrorCodeFillFieldsScriptFailed = 5, - AppExtensionErrorCodeUnexpectedData = 6, - AppExtensionErrorCodeFailedToObtainURLStringFromWebView = 7 -}; - -// Note to creators of libraries or frameworks: -// If you include this code within your library, then to prevent potential duplicate symbol -// conflicts for adopters of your library, you should rename the OnePasswordExtension class -// and associated typedefs. You might to so by adding your own project prefix, e.g., -// MyLibraryOnePasswordExtension. - -typedef void (^OnePasswordLoginDictionaryCompletionBlock)(NSDictionary * __nullable loginDictionary, NSError * __nullable error); -typedef void (^OnePasswordSuccessCompletionBlock)(BOOL success, NSError * __nullable error); -typedef void (^OnePasswordExtensionItemCompletionBlock)(NSExtensionItem * __nullable extensionItem, NSError * __nullable error); - -@interface OnePasswordExtension : NSObject - -+ (OnePasswordExtension *)sharedExtension; - -/*! - @discussion Determines if the 1Password Extension is available. Allows you to only show the 1Password login button to those - that can use it. Of course, you could leave the button enabled and educate users about the virtues of strong, unique - passwords instead :) - - @return isAppExtensionAvailable Returns YES if any app that supports the generic `org-appextension-feature-password-management` feature is installed on the device. - */ -#ifdef __IPHONE_8_0 -- (BOOL)isAppExtensionAvailable NS_EXTENSION_UNAVAILABLE_IOS("Not available in an extension. Check if org-appextension-feature-password-management:// URL can be opened by the app."); -#else -- (BOOL)isAppExtensionAvailable; -#endif - -/*! - Called from your login page, this method will find all available logins for the given URLString. - - @discussion 1Password will show all matching Login for the naked domain of the given URLString. For example if the user has an item in your 1Password vault with "subdomain1.domain.com” as the website and another one with "subdomain2.domain.com”, and the URLString is "https://domain.com", 1Password will show both items. - - However, if no matching login is found for "https://domain.com", the 1Password Extension will display the "Show all Logins" button so that the user can search among all the Logins in the vault. This is especially useful when the user has a login for "https://olddomain.com". - - After the user selects a login, it is stored into an NSDictionary and given to your completion handler. Use the `Login Dictionary keys` above to - extract the needed information and update your UI. The completion block is guaranteed to be called on the main thread. - - @param URLString For the matching Logins in the 1Password vault. - - @param viewController The view controller from which the 1Password Extension is invoked. Usually `self` - - @param sender The sender which triggers the share sheet to show. UIButton, UIBarButtonItem or UIView. Can also be nil on iPhone, but not on iPad. - - @param completion A completion block called with two parameters loginDictionary and error once completed. The loginDictionary reply parameter that contains the username, password and the One-Time Password if available. The error Reply parameter that is nil if the 1Password Extension has been successfully completed, or it contains error information about the completion failure. - */ -- (void)findLoginForURLString:(nonnull NSString *)URLString forViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender completion:(nonnull OnePasswordLoginDictionaryCompletionBlock)completion; - -/*! - Create a new login within 1Password and allow the user to generate a new password before saving. - - @discussion The provided URLString should be unique to your app or service and be identical to what you pass into the find login method. - The completion block is guaranteed to be called on the main - thread. - - @param URLString For the new Login to be saved in 1Password. - - @param loginDetailsDictionary about the Login to be saved, including custom fields, are stored in an dictionary and given to the 1Password Extension. - - @param passwordGenerationOptions The Password generator options represented in a dictionary form. - - @param viewController The view controller from which the 1Password Extension is invoked. Usually `self` - - @param sender The sender which triggers the share sheet to show. UIButton, UIBarButtonItem or UIView. Can also be nil on iPhone, but not on iPad. - - @param completion A completion block which is called with type parameters loginDictionary and error. The loginDictionary reply parameter which contain all the information about the newly saved Login. Use the `Login Dictionary keys` above to extract the needed information and update your UI. For example, updating the UI with the newly generated password lets the user know their action was successful. The error reply parameter that is nil if the 1Password Extension has been successfully completed, or it contains error information about the completion failure. - */ -- (void)storeLoginForURLString:(nonnull NSString *)URLString loginDetails:(nullable NSDictionary *)loginDetailsDictionary passwordGenerationOptions:(nullable NSDictionary *)passwordGenerationOptions forViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender completion:(nonnull OnePasswordLoginDictionaryCompletionBlock)completion; - -/*! - Change the password for an existing login within 1Password. - - @discussion The provided URLString should be unique to your app or service and be identical to what you pass into the find login method. The completion block is guaranteed to be called on the main thread. - - 1Password 6 and later: - The 1Password Extension will display all available the matching Logins for the given URL string. The user can choose which Login item to update. The "New Login" button will also be available at all times, in case the user wishes to to create a new Login instead, - - 1Password 5: - These are the three scenarios that are supported: - 1. A single matching Login is found: 1Password will enter edit mode for that Login and will update its password using the value for AppExtensionPasswordKey. - 2. More than a one matching Logins are found: 1Password will display a list of all matching Logins. The user must choose which one to update. Once in edit mode, the Login will be updated with the new password. - 3. No matching login is found: 1Password will create a new Login using the optional fields if available to populate its properties. - - @param URLString for the Login to be updated with a new password in 1Password. - - @param loginDetailsDictionary about the Login to be saved, including old password and the username, are stored in an dictionary and given to the 1Password Extension. - - @param passwordGenerationOptions The Password generator options represented in a dictionary form. - - @param viewController The view controller from which the 1Password Extension is invoked. Usually `self` - - @param sender The sender which triggers the share sheet to show. UIButton, UIBarButtonItem or UIView. Can also be nil on iPhone, but not on iPad. - - @param completion A completion block which is called with type parameters loginDictionary and error. The loginDictionary reply parameter which contain all the information about the newly updated Login, including the newly generated and the old password. Use the `Login Dictionary keys` above to extract the needed information and update your UI. For example, updating the UI with the newly generated password lets the user know their action was successful. The error reply parameter that is nil if the 1Password Extension has been successfully completed, or it contains error information about the completion failure. - */ -- (void)changePasswordForLoginForURLString:(nonnull NSString *)URLString loginDetails:(nullable NSDictionary *)loginDetailsDictionary passwordGenerationOptions:(nullable NSDictionary *)passwordGenerationOptions forViewController:(UIViewController *)viewController sender:(nullable id)sender completion:(nonnull OnePasswordLoginDictionaryCompletionBlock)completion; - -/*! - Called from your web view controller, this method will show all the saved logins for the active page in the provided web - view, and automatically fill the HTML form fields. Supports WKWebView. - - @discussion 1Password will show all matching Login for the naked domain of the current website. For example if the user has an item in your 1Password vault with "subdomain1.domain.com” as the website and another one with "subdomain2.domain.com”, and the current website is "https://domain.com", 1Password will show both items. - - However, if no matching login is found for "https://domain.com", the 1Password Extension will display the "New Login" button so that the user can create a new Login for the current website. - - @param webView The web view which displays the form to be filled. The active WKWebView. Must not be nil. - - @param viewController The view controller from which the 1Password Extension is invoked. Usually `self` - - @param sender The sender which triggers the share sheet to show. UIButton, UIBarButtonItem or UIView. Can also be nil on iPhone, but not on iPad. - - @param yesOrNo Boolean flag. If YES is passed only matching Login items will be shown, otherwise the 1Password Extension will also display Credit Cards and Identities. - - @param completion Completion block called on completion with parameters success, and error. The success reply parameter that is YES if the 1Password Extension has been successfully completed or NO otherwise. The error reply parameter that is nil if the 1Password Extension has been successfully completed, or it contains error information about the completion failure. - */ -- (void)fillItemIntoWebView:(nonnull WKWebView *)webView forViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender showOnlyLogins:(BOOL)yesOrNo completion:(nonnull OnePasswordSuccessCompletionBlock)completion; - -/*! - Called in the UIActivityViewController completion block to find out whether or not the user selected the 1Password Extension activity. - - @param activityType or the bundle identifier of the selected activity in the share sheet. - - @return isOnePasswordExtensionActivityType Returns YES if the selected activity is the 1Password extension, NO otherwise. - */ -- (BOOL)isOnePasswordExtensionActivityType:(nullable NSString *)activityType; - -/*! - The returned NSExtensionItem can be used to create your own UIActivityViewController. Use `isOnePasswordExtensionActivityType:` and `fillReturnedItems:intoWebView:completion:` in the activity view controller completion block to process the result. The completion block is guaranteed to be called on the main thread. - - @param webView The web view which displays the form to be filled. The active WKWebView. Must not be nil. - - @param completion Completion block called on completion with extensionItem and error. The extensionItem reply parameter that is contains all the info required by the 1Password extension if has been successfully completed or nil otherwise. The error reply parameter that is nil if the 1Password extension item has been successfully created, or it contains error information about the completion failure. - */ -- (void)createExtensionItemForWebView:(nonnull WKWebView *)webView completion:(nonnull OnePasswordExtensionItemCompletionBlock)completion; - -/*! - Method used in the UIActivityViewController completion block to fill information into a web view. - - @param returnedItems Array which contains the selected activity in the share sheet. Empty array if the share sheet is cancelled by the user. - @param webView The web view which displays the form to be filled. The active WKWebView. Must not be nil. - - @param completion Completion block called on completion with parameters success, and error. The success reply parameter that is YES if the 1Password Extension has been successfully completed or NO otherwise. The error reply parameter that is nil if the 1Password Extension has been successfully completed, or it contains error information about the completion failure. - */ -- (void)fillReturnedItems:(nullable NSArray *)returnedItems intoWebView:(nonnull WKWebView *)webView completion:(nonnull OnePasswordSuccessCompletionBlock)completion; -@end - -#if __has_feature(nullability) -NS_ASSUME_NONNULL_END -#endif diff --git a/Shared/1Password/OnePasswordExtension.m b/Shared/1Password/OnePasswordExtension.m deleted file mode 100644 index 5f8531632..000000000 --- a/Shared/1Password/OnePasswordExtension.m +++ /dev/null @@ -1,638 +0,0 @@ -//Copyright (c) 2014-2020 AgileBits Inc. -// -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: -// -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. -// -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. - -#import "OnePasswordExtension.h" - -NSString *const AppExtensionURLStringKey = @"url_string"; -NSString *const AppExtensionUsernameKey = @"username"; -NSString *const AppExtensionPasswordKey = @"password"; -NSString *const AppExtensionTOTPKey = @"totp"; -NSString *const AppExtensionTitleKey = @"login_title"; -NSString *const AppExtensionNotesKey = @"notes"; -NSString *const AppExtensionSectionTitleKey = @"section_title"; -NSString *const AppExtensionFieldsKey = @"fields"; -NSString *const AppExtensionReturnedFieldsKey = @"returned_fields"; -NSString *const AppExtensionOldPasswordKey = @"old_password"; -NSString *const AppExtensionPasswordGeneratorOptionsKey = @"password_generator_options"; - -NSString *const AppExtensionGeneratedPasswordMinLengthKey = @"password_min_length"; -NSString *const AppExtensionGeneratedPasswordMaxLengthKey = @"password_max_length"; -NSString *const AppExtensionGeneratedPasswordRequireDigitsKey = @"password_require_digits"; -NSString *const AppExtensionGeneratedPasswordRequireSymbolsKey = @"password_require_symbols"; -NSString *const AppExtensionGeneratedPasswordForbiddenCharactersKey = @"password_forbidden_characters"; - -NSString *const AppExtensionErrorDomain = @"OnePasswordExtension"; - -// Version -#define VERSION_NUMBER @(185) -static NSString *const AppExtensionVersionNumberKey = @"version_number"; - -// Available App Extension Actions -static NSString *const kUTTypeAppExtensionFindLoginAction = @"org.appextension.find-login-action"; -static NSString *const kUTTypeAppExtensionSaveLoginAction = @"org.appextension.save-login-action"; -static NSString *const kUTTypeAppExtensionChangePasswordAction = @"org.appextension.change-password-action"; -static NSString *const kUTTypeAppExtensionFillWebViewAction = @"org.appextension.fill-webview-action"; -static NSString *const kUTTypeAppExtensionFillBrowserAction = @"org.appextension.fill-browser-action"; - -// WebView Dictionary keys -static NSString *const AppExtensionWebViewPageFillScript = @"fillScript"; -static NSString *const AppExtensionWebViewPageDetails = @"pageDetails"; - -@implementation OnePasswordExtension - -#pragma mark - Public Methods - -+ (OnePasswordExtension *)sharedExtension { - static dispatch_once_t onceToken; - static OnePasswordExtension *__sharedExtension; - - dispatch_once(&onceToken, ^{ - __sharedExtension = [OnePasswordExtension new]; - }); - - return __sharedExtension; -} - -- (BOOL)isAppExtensionAvailable { - if ([self isSystemAppExtensionAPIAvailable]) { - return [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"org-appextension-feature-password-management://"]]; - } - - return NO; -} - -#pragma mark - Native app Login - -- (void)findLoginForURLString:(nonnull NSString *)URLString forViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender completion:(nonnull OnePasswordLoginDictionaryCompletionBlock)completion { - NSAssert(URLString != nil, @"URLString must not be nil"); - NSAssert(viewController != nil, @"viewController must not be nil"); - - if (NO == [self isSystemAppExtensionAPIAvailable]) { - NSLog(@"Failed to findLoginForURLString, system API is not available"); - if (completion) { - completion(nil, [OnePasswordExtension systemAppExtensionAPINotAvailableError]); - } - - return; - } - - NSDictionary *item = @{ AppExtensionVersionNumberKey: VERSION_NUMBER, AppExtensionURLStringKey: URLString }; - - UIActivityViewController *activityViewController = [self activityViewControllerForItem:item viewController:viewController sender:sender typeIdentifier:kUTTypeAppExtensionFindLoginAction]; - activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { - if (returnedItems.count == 0) { - NSError *error = nil; - if (activityError) { - NSLog(@"Failed to findLoginForURLString: %@", activityError); - error = [OnePasswordExtension failedToContactExtensionErrorWithActivityError:activityError]; - } - else { - error = [OnePasswordExtension extensionCancelledByUserError]; - } - - if (completion) { - completion(nil, error); - } - - return; - } - - [self processExtensionItem:returnedItems.firstObject completion:^(NSDictionary *itemDictionary, NSError *error) { - if (completion) { - completion(itemDictionary, error); - } - }]; - }; - - [viewController presentViewController:activityViewController animated:YES completion:nil]; -} - -#pragma mark - New User Registration - -- (void)storeLoginForURLString:(nonnull NSString *)URLString loginDetails:(nullable NSDictionary *)loginDetailsDictionary passwordGenerationOptions:(nullable NSDictionary *)passwordGenerationOptions forViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender completion:(nonnull OnePasswordLoginDictionaryCompletionBlock)completion { - NSAssert(URLString != nil, @"URLString must not be nil"); - NSAssert(viewController != nil, @"viewController must not be nil"); - - if (NO == [self isSystemAppExtensionAPIAvailable]) { - NSLog(@"Failed to storeLoginForURLString, system API is not available"); - if (completion) { - completion(nil, [OnePasswordExtension systemAppExtensionAPINotAvailableError]); - } - - return; - } - - NSMutableDictionary *newLoginAttributesDict = [NSMutableDictionary new]; - newLoginAttributesDict[AppExtensionVersionNumberKey] = VERSION_NUMBER; - newLoginAttributesDict[AppExtensionURLStringKey] = URLString; - [newLoginAttributesDict addEntriesFromDictionary:loginDetailsDictionary]; - if (passwordGenerationOptions.count > 0) { - newLoginAttributesDict[AppExtensionPasswordGeneratorOptionsKey] = passwordGenerationOptions; - } - - UIActivityViewController *activityViewController = [self activityViewControllerForItem:newLoginAttributesDict viewController:viewController sender:sender typeIdentifier:kUTTypeAppExtensionSaveLoginAction]; - activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { - if (returnedItems.count == 0) { - NSError *error = nil; - if (activityError) { - NSLog(@"Failed to storeLoginForURLString: %@", activityError); - error = [OnePasswordExtension failedToContactExtensionErrorWithActivityError:activityError]; - } - else { - error = [OnePasswordExtension extensionCancelledByUserError]; - } - - if (completion) { - completion(nil, error); - } - - return; - } - - [self processExtensionItem:returnedItems.firstObject completion:^(NSDictionary *itemDictionary, NSError *error) { - if (completion) { - completion(itemDictionary, error); - } - }]; - }; - - [viewController presentViewController:activityViewController animated:YES completion:nil]; -} - -#pragma mark - Change Password - -- (void)changePasswordForLoginForURLString:(nonnull NSString *)URLString loginDetails:(nullable NSDictionary *)loginDetailsDictionary passwordGenerationOptions:(nullable NSDictionary *)passwordGenerationOptions forViewController:(UIViewController *)viewController sender:(nullable id)sender completion:(nonnull OnePasswordLoginDictionaryCompletionBlock)completion { - NSAssert(URLString != nil, @"URLString must not be nil"); - NSAssert(viewController != nil, @"viewController must not be nil"); - - if (NO == [self isSystemAppExtensionAPIAvailable]) { - NSLog(@"Failed to changePasswordForLoginWithUsername, system API is not available"); - if (completion) { - completion(nil, [OnePasswordExtension systemAppExtensionAPINotAvailableError]); - } - - return; - } - - NSMutableDictionary *item = [NSMutableDictionary new]; - item[AppExtensionVersionNumberKey] = VERSION_NUMBER; - item[AppExtensionURLStringKey] = URLString; - [item addEntriesFromDictionary:loginDetailsDictionary]; - if (passwordGenerationOptions.count > 0) { - item[AppExtensionPasswordGeneratorOptionsKey] = passwordGenerationOptions; - } - - UIActivityViewController *activityViewController = [self activityViewControllerForItem:item viewController:viewController sender:sender typeIdentifier:kUTTypeAppExtensionChangePasswordAction]; - - activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { - if (returnedItems.count == 0) { - NSError *error = nil; - if (activityError) { - NSLog(@"Failed to changePasswordForLoginWithUsername: %@", activityError); - error = [OnePasswordExtension failedToContactExtensionErrorWithActivityError:activityError]; - } - else { - error = [OnePasswordExtension extensionCancelledByUserError]; - } - - if (completion) { - completion(nil, error); - } - - return; - } - - [self processExtensionItem:returnedItems.firstObject completion:^(NSDictionary *itemDictionary, NSError *error) { - if (completion) { - completion(itemDictionary, error); - } - }]; - }; - - [viewController presentViewController:activityViewController animated:YES completion:nil]; -} - -#pragma mark - Web View filling Support - -- (void)fillItemIntoWebView:(nonnull WKWebView *)webView forViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender showOnlyLogins:(BOOL)yesOrNo completion:(nonnull OnePasswordSuccessCompletionBlock)completion { - NSAssert(webView != nil, @"webView must not be nil"); - NSAssert(viewController != nil, @"viewController must not be nil"); - NSAssert([webView isKindOfClass:[WKWebView class]], @"webView must be an instance of WKWebView."); - - [self fillItemIntoWKWebView:webView forViewController:viewController sender:(id)sender showOnlyLogins:yesOrNo completion:^(BOOL success, NSError *error) { - if (completion) { - completion(success, error); - } - }]; -} - -#pragma mark - Support for custom UIActivityViewControllers - -- (BOOL)isOnePasswordExtensionActivityType:(nullable NSString *)activityType { - return [@"com.agilebits.onepassword-ios.extension" isEqualToString:activityType] || [@"com.agilebits.beta.onepassword-ios.extension" isEqualToString:activityType]; -} - -- (void)createExtensionItemForWebView:(nonnull WKWebView *)webView completion:(nonnull OnePasswordExtensionItemCompletionBlock)completion { - NSAssert(webView != nil, @"webView must not be nil"); - NSAssert([webView isKindOfClass:[WKWebView class]], @"webView must be an instance of WKWebView."); - - [webView evaluateJavaScript:OPWebViewCollectFieldsScript completionHandler:^(NSString *result, NSError *evaluateError) { - if (result == nil) { - NSLog(@"1Password Extension failed to collect web page fields: %@", evaluateError); - NSError *failedToCollectFieldsError = [OnePasswordExtension failedToCollectFieldsErrorWithUnderlyingError:evaluateError]; - if (completion) { - if ([NSThread isMainThread]) { - completion(nil, failedToCollectFieldsError); - } - else { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(nil, failedToCollectFieldsError); - }); - } - } - - return; - } - - [self createExtensionItemForURLString:webView.URL.absoluteString webPageDetails:result completion:completion]; - }]; -} - -- (void)fillReturnedItems:(nullable NSArray *)returnedItems intoWebView:(nonnull WKWebView *)webView completion:(nonnull OnePasswordSuccessCompletionBlock)completion { - NSAssert(webView != nil, @"webView must not be nil"); - - if (returnedItems.count == 0) { - NSError *error = [OnePasswordExtension extensionCancelledByUserError]; - if (completion) { - completion(NO, error); - } - - return; - } - - [self processExtensionItem:returnedItems.firstObject completion:^(NSDictionary *itemDictionary, NSError *error) { - if (itemDictionary.count == 0) { - if (completion) { - completion(NO, error); - } - - return; - } - - NSString *fillScript = itemDictionary[AppExtensionWebViewPageFillScript]; - [self executeFillScript:fillScript inWebView:webView completion:^(BOOL success, NSError *executeFillScriptError) { - if (completion) { - completion(success, executeFillScriptError); - } - }]; - }]; -} - -#pragma mark - Private methods - -- (BOOL)isSystemAppExtensionAPIAvailable { - return [NSExtensionItem class] != nil; -} - -- (void)findLoginIn1PasswordWithURLString:(nonnull NSString *)URLString collectedPageDetails:(nullable NSString *)collectedPageDetails forWebViewController:(nonnull UIViewController *)forViewController sender:(nullable id)sender withWebView:(nonnull WKWebView *)webView showOnlyLogins:(BOOL)yesOrNo completion:(nonnull OnePasswordSuccessCompletionBlock)completion { - if ([URLString length] == 0) { - NSError *URLStringError = [OnePasswordExtension failedToObtainURLStringFromWebViewError]; - NSLog(@"Failed to findLoginIn1PasswordWithURLString: %@", URLStringError); - if (completion) { - completion(NO, URLStringError); - } - return; - } - - NSError *jsonError = nil; - NSData *data = [collectedPageDetails dataUsingEncoding:NSUTF8StringEncoding]; - NSDictionary *collectedPageDetailsDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError]; - - if (collectedPageDetailsDictionary.count == 0) { - NSLog(@"Failed to parse JSON collected page details: %@", jsonError); - if (completion) { - completion(NO, jsonError); - } - return; - } - - NSDictionary *item = @{ AppExtensionVersionNumberKey : VERSION_NUMBER, AppExtensionURLStringKey : URLString, AppExtensionWebViewPageDetails : collectedPageDetailsDictionary }; - - NSString *typeIdentifier = yesOrNo ? kUTTypeAppExtensionFillWebViewAction : kUTTypeAppExtensionFillBrowserAction; - UIActivityViewController *activityViewController = [self activityViewControllerForItem:item viewController:forViewController sender:sender typeIdentifier:typeIdentifier]; - activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { - if (returnedItems.count == 0) { - NSError *error = nil; - if (activityError) { - NSLog(@"Failed to findLoginIn1PasswordWithURLString: %@", activityError); - error = [OnePasswordExtension failedToContactExtensionErrorWithActivityError:activityError]; - } - else { - error = [OnePasswordExtension extensionCancelledByUserError]; - } - - if (completion) { - completion(NO, error); - } - - return; - } - - [self processExtensionItem:returnedItems.firstObject completion:^(NSDictionary *itemDictionary, NSError *processExtensionItemError) { - if (itemDictionary.count == 0) { - if (completion) { - completion(NO, processExtensionItemError); - } - - return; - } - - NSString *fillScript = itemDictionary[AppExtensionWebViewPageFillScript]; - [self executeFillScript:fillScript inWebView:webView completion:^(BOOL success, NSError *executeFillScriptError) { - if (completion) { - completion(success, executeFillScriptError); - } - }]; - }]; - }; - - [forViewController presentViewController:activityViewController animated:YES completion:nil]; -} - -- (void)fillItemIntoWKWebView:(nonnull WKWebView *)webView forViewController:(nonnull UIViewController *)viewController sender:(nullable id)sender showOnlyLogins:(BOOL)yesOrNo completion:(nonnull OnePasswordSuccessCompletionBlock)completion { - [webView evaluateJavaScript:OPWebViewCollectFieldsScript completionHandler:^(NSString *result, NSError *error) { - if (result == nil) { - NSLog(@"1Password Extension failed to collect web page fields: %@", error); - if (completion) { - completion(NO,[OnePasswordExtension failedToCollectFieldsErrorWithUnderlyingError:error]); - } - - return; - } - - [self findLoginIn1PasswordWithURLString:webView.URL.absoluteString collectedPageDetails:result forWebViewController:viewController sender:sender withWebView:webView showOnlyLogins:yesOrNo completion:^(BOOL success, NSError *findLoginError) { - if (completion) { - completion(success, findLoginError); - } - }]; - }]; -} - -- (void)executeFillScript:(NSString * __nullable)fillScript inWebView:(nonnull WKWebView *)webView completion:(nonnull OnePasswordSuccessCompletionBlock)completion { - - if (fillScript == nil) { - NSLog(@"Failed to executeFillScript, fillScript is missing"); - if (completion) { - completion(NO, [OnePasswordExtension failedToFillFieldsErrorWithLocalizedErrorMessage:NSLocalizedStringFromTable(@"Failed to fill web page because script is missing", @"OnePasswordExtension", @"1Password Extension Error Message") underlyingError:nil]); - } - - return; - } - - NSMutableString *scriptSource = [OPWebViewFillScript mutableCopy]; - [scriptSource appendFormat:@"(document, %@, undefined);", fillScript]; - - [webView evaluateJavaScript:scriptSource completionHandler:^(NSString *result, NSError *evaluationError) { - BOOL success = (result != nil); - NSError *error = nil; - - if (!success) { - NSLog(@"Cannot executeFillScript, evaluateJavaScript failed: %@", evaluationError); - error = [OnePasswordExtension failedToFillFieldsErrorWithLocalizedErrorMessage:NSLocalizedStringFromTable(@"Failed to fill web page because script could not be evaluated", @"OnePasswordExtension", @"1Password Extension Error Message") underlyingError:error]; - } - - if (completion) { - completion(success, error); - } - }]; -} - -- (void)processExtensionItem:(nullable NSExtensionItem *)extensionItem completion:(nonnull OnePasswordLoginDictionaryCompletionBlock)completion { - if (extensionItem.attachments.count == 0) { - NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Unexpected data returned by App Extension: extension item had no attachments." }; - NSError *error = [[NSError alloc] initWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeUnexpectedData userInfo:userInfo]; - if (completion) { - completion(nil, error); - } - return; - } - - NSItemProvider *itemProvider = extensionItem.attachments.firstObject; - if (NO == [itemProvider hasItemConformingToTypeIdentifier:(__bridge NSString *)kUTTypePropertyList]) { - NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Unexpected data returned by App Extension: extension item attachment does not conform to kUTTypePropertyList type identifier" }; - NSError *error = [[NSError alloc] initWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeUnexpectedData userInfo:userInfo]; - if (completion) { - completion(nil, error); - } - return; - } - - - [itemProvider loadItemForTypeIdentifier:(__bridge NSString *)kUTTypePropertyList options:nil completionHandler:^(NSDictionary *itemDictionary, NSError *itemProviderError) { - NSError *error = nil; - if (itemDictionary.count == 0) { - NSLog(@"Failed to loadItemForTypeIdentifier: %@", itemProviderError); - error = [OnePasswordExtension failedToLoadItemProviderDataErrorWithUnderlyingError:itemProviderError]; - } - - if (completion) { - if ([NSThread isMainThread]) { - completion(itemDictionary, error); - } - else { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(itemDictionary, error); - }); - } - } - }]; -} - -- (UIActivityViewController *)activityViewControllerForItem:(nonnull NSDictionary *)item viewController:(nonnull UIViewController*)viewController sender:(nullable id)sender typeIdentifier:(nonnull NSString *)typeIdentifier { - NSAssert(NO == (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad && sender == nil), @"sender must not be nil on iPad."); - - NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithItem:item typeIdentifier:typeIdentifier]; - - NSExtensionItem *extensionItem = [[NSExtensionItem alloc] init]; - extensionItem.attachments = @[ itemProvider ]; - - UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:@[ extensionItem ] applicationActivities:nil]; - - if ([sender isKindOfClass:[UIBarButtonItem class]]) { - controller.popoverPresentationController.barButtonItem = sender; - } - else if ([sender isKindOfClass:[UIView class]]) { - controller.popoverPresentationController.sourceView = [sender superview]; - controller.popoverPresentationController.sourceRect = [sender frame]; - } - else { - NSLog(@"sender can be nil on iPhone"); - } - - return controller; -} - -- (void)createExtensionItemForURLString:(nonnull NSString *)URLString webPageDetails:(nullable NSString *)webPageDetails completion:(nonnull OnePasswordExtensionItemCompletionBlock)completion { - NSError *jsonError = nil; - NSData *data = [webPageDetails dataUsingEncoding:NSUTF8StringEncoding]; - NSDictionary *webPageDetailsDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError]; - - if (webPageDetailsDictionary.count == 0) { - NSLog(@"Failed to parse JSON collected page details: %@", jsonError); - if (completion) { - completion(nil, jsonError); - } - return; - } - - NSDictionary *item = @{ AppExtensionVersionNumberKey : VERSION_NUMBER, AppExtensionURLStringKey : URLString, AppExtensionWebViewPageDetails : webPageDetailsDictionary }; - - NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithItem:item typeIdentifier:kUTTypeAppExtensionFillBrowserAction]; - - NSExtensionItem *extensionItem = [[NSExtensionItem alloc] init]; - extensionItem.attachments = @[ itemProvider ]; - - if (completion) { - if ([NSThread isMainThread]) { - completion(extensionItem, nil); - } - else { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(extensionItem, nil); - }); - } - } -} - -#pragma mark - Errors - -+ (NSError *)systemAppExtensionAPINotAvailableError { - NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"App Extension API is not available in this version of iOS", @"OnePasswordExtension", @"1Password Extension Error Message") }; - return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeAPINotAvailable userInfo:userInfo]; -} - - -+ (NSError *)extensionCancelledByUserError { - NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"1Password Extension was cancelled by the user", @"OnePasswordExtension", @"1Password Extension Error Message") }; - return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeCancelledByUser userInfo:userInfo]; -} - -+ (NSError *)failedToContactExtensionErrorWithActivityError:(nullable NSError *)activityError { - NSMutableDictionary *userInfo = [NSMutableDictionary new]; - userInfo[NSLocalizedDescriptionKey] = NSLocalizedStringFromTable(@"Failed to contact the 1Password Extension", @"OnePasswordExtension", @"1Password Extension Error Message"); - if (activityError) { - userInfo[NSUnderlyingErrorKey] = activityError; - } - - return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeFailedToContactExtension userInfo:userInfo]; -} - -+ (NSError *)failedToCollectFieldsErrorWithUnderlyingError:(nullable NSError *)underlyingError { - NSMutableDictionary *userInfo = [NSMutableDictionary new]; - userInfo[NSLocalizedDescriptionKey] = NSLocalizedStringFromTable(@"Failed to execute script that collects web page information", @"OnePasswordExtension", @"1Password Extension Error Message"); - if (underlyingError) { - userInfo[NSUnderlyingErrorKey] = underlyingError; - } - - return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeCollectFieldsScriptFailed userInfo:userInfo]; -} - -+ (NSError *)failedToFillFieldsErrorWithLocalizedErrorMessage:(nullable NSString *)errorMessage underlyingError:(nullable NSError *)underlyingError { - NSMutableDictionary *userInfo = [NSMutableDictionary new]; - if (errorMessage) { - userInfo[NSLocalizedDescriptionKey] = errorMessage; - } - if (underlyingError) { - userInfo[NSUnderlyingErrorKey] = underlyingError; - } - - return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeFillFieldsScriptFailed userInfo:userInfo]; -} - -+ (NSError *)failedToLoadItemProviderDataErrorWithUnderlyingError:(nullable NSError *)underlyingError { - NSMutableDictionary *userInfo = [NSMutableDictionary new]; - userInfo[NSLocalizedDescriptionKey] = NSLocalizedStringFromTable(@"Failed to parse information returned by 1Password Extension", @"OnePasswordExtension", @"1Password Extension Error Message"); - if (underlyingError) { - userInfo[NSUnderlyingErrorKey] = underlyingError; - } - - return [[NSError alloc] initWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeFailedToLoadItemProviderData userInfo:userInfo]; -} - -+ (NSError *)failedToObtainURLStringFromWebViewError { - NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"Failed to obtain URL String from web view. The web view must be loaded completely when calling the 1Password Extension", @"OnePasswordExtension", @"1Password Extension Error Message") }; - return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeFailedToObtainURLStringFromWebView userInfo:userInfo]; -} - -#pragma mark - WebView field collection and filling scripts - -static NSString *const OPWebViewCollectFieldsScript = @";(function(document, undefined) {\ -\ - document.addEventListener('input',function(b){!1!==b.isTrusted&&'input'===b.target.tagName.toLowerCase()&&(b.target.dataset['com.agilebits.onepassword.userEdited']='yes')},!0);\ -(function(b,a,c){a.FieldCollector=new function(){function f(d){return d?d.toString().toLowerCase():''}function e(d,b,a,e){e!==c&&e===a||null===a||a===c||(d[b]=a)}function k(d,b){var a=[];try{a=d.querySelectorAll(b)}catch(J){console.error('[COLLECT FIELDS] @ag_querySelectorAll Exception in selector \"'+b+'\"')}return a}function m(d){var a,c=[];if(d.labels&&d.labels.length&&0=a.cells.length)return null;d=r(a.cells[d.cellIndex]);return d=l(d)}function p(a){return a.options?(a=Array.prototype.slice.call(a.options).map(function(a){var d=a.text,d=d?f(d).replace(/\\s/mg,'').replace(/[~`!@$%^&*()\\-_+=:;'\"\\[\\]|\\\\,<.>\\/?]/mg,\ -''):null;return[d?d:null,a.value]}),{options:a}):null}function F(a){switch(f(a.type)){case 'checkbox':return a.checked?'✓':'';case 'hidden':a=a.value;if(!a||'number'!=typeof a.length)return'';254b.clientWidth||10>b.clientHeight)return!1;var n=b.getClientRects();if(0===n.length)return!1;for(var p=0;pf||0>m.right)return!1;if(0>k||k>f||0>a||a>e)return!1;for(c=b.ownerDocument.elementFromPoint(k+(c.right>window.innerWidth?(window.innerWidth-k)/2:c.width/2),a+(c.bottom>window.innerHeight?\ -(window.innerHeight-a)/2:c.height/2));c&&c!==b&&c!==document;){if(c.tagName&&'string'===typeof c.tagName&&'label'===c.tagName.toLowerCase()&&b.labels&&0 URL? { switch accountType { case .freshRSS: diff --git a/iOS/Resources/Assets.xcassets/1password.imageset/Contents.json b/iOS/Resources/Assets.xcassets/1password.imageset/Contents.json deleted file mode 100644 index 4bf18a9e4..000000000 --- a/iOS/Resources/Assets.xcassets/1password.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "onepassword-navbar.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "onepassword-navbar@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "onepassword-navbar@3x.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/iOS/Resources/Assets.xcassets/1password.imageset/onepassword-navbar.png b/iOS/Resources/Assets.xcassets/1password.imageset/onepassword-navbar.png deleted file mode 100644 index d20bc7dfd2796a64fec6a7363c6d576017486d99..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 770 zcmV+d1O5DoP)Px%yh%hsR7ee#l|640K@f&_?-C~gQJMJeoW>Cyoe>gEYzZMm84w*GB?u}I5}`E( zLIfgAhs1w?7z!GYkduOjKu1B4+{9-mSfYXOA>Z1~X`M!H8KpMRRgo*c@2@aN6>s>&TQ-w9t!(=NmOOnM&#DT7nzF1G zW14Xm#{#w~Y->Rf+-WwOZ{kdY8k^mgvz}+NuKOkHI2W=?9TIwJHL){3?%6Dv zNQ{Z$jlh^izuA1&GmbKHu}`_|C3tQ+j&n+U#uGG&2a>Wtf5w=fB_OPCO|AD*;XVKyaGo1$#MnvX> zZkbJ-h^P}qh-<<-hEtl}NjIB%Lwlb&{>CKP+wfzS$9$Hw^&UKF!qHM3zrDVn!mNRDq%70AXwl8 zN2qT1`nb_&JpKFZe3pl_AiSNzC54{Vp0L1e;(2v+TBRt{*CDPx*U`a$lRA>d&noVp}RTRhHeKW09sWDC4ndwF#YJxE_iVHUiF)j!iDSifHB5Q<= zG!TPgRgB>yEF|I3^L>2->2!LaTCEOXP8%s2#GLL9%K-5hZT4eEKZNU|^2-744rH^38EmLVfYy1m zb;5Dov-y1fLQP$R3}$v8E4H1W*!k|_()NZ`Jve>I>Hq7iZsA%}vU zLwuyHP#eiEwE-0-`4*K28P}5~;OEQbN1`+zHJF>rX1}4YXC4Up>i&KHYkB0!iT zcOaLmm`pu7na@X!X}hq}&$~)}Ou?;Qq1N9{77E+oYi!=NJ$yzsq6ICQZVWmsqNzM) z_$3?k6^33|8Ks_LGVh0l)&Q3yujcd+^Qf=su)LP!1w700t`Wux>;v25CNDGyT(Lop z*h)5H1Dm!@+reNS!>HVvCwGWovu0+2%Z*6Ig93t8nvv5(_*G20TrTedi+ALLcq3YW z(cm$_JLMWTh0OG5tB*V*I}V+mo-P3Rxaot*ya(SQc@H?oFAmc>DPV>Lezvict!CmP zlFQD!H^8_)m&-jZcs$?~$Z@lfwEiH$jfiZ0$%tqrl`|X*_|VGK+QTZq(=3O$rV9z* z!>}=^oL!sS1s>yiSko>BzX`_p(-UzWCNnwq#9!?KUn~}XM=2Nmv3fA=M_vEr zvQUO=YJeo6KKPwU`ZnyNR1n)1F%|1Xi9g*?K%E9?LL9SGb(%C&Ww8NzgUd}RZ$vN2 zBf#)+sSo-mZ1AqQeV5Z7J72^GX3@AjY`r8e7sJP-;?A`Vv|!=p^j=iLWA8lz+`Pse z^W2ikm|_fLknC+4<{8| zx1Ulwn*r_1mY9r}aJk1_E}id80cZN6vte7crGG&e4cp$XmL)-SRDD4EQ7>)2?eVv0nsh zS%Aw;wCKPnwFi&!_9O!|>LcJAwF%ev_V#w8*j={VceXs>sQ#Sne5piumfv36aFv;k zRB9i1`UD0T|HSQ8;}HiBx(+f5~YqFmRSuEfm2_G56|8sJhv z1Zh8;U{rXtE~fG+;GdQJN+eeK1tMyv>{tXnIXooLUy}sb4Jj(j%o+_NK;n6Jxo+s# zT!Dm{-$q;3JW*h*Ftazj+;YsSCa{m7uUNKMS+-(x7;Ag;Wg?4b`Vr$XDPXnrV?r#`SA+i zQ)uJ_^b9669_mv!%jUZupxYUIH?-!ykd5fubWI>XEO0r%y*>=K^yfmY|De%1PNcCa z5AI5(BJV@*am&QK&xJ0Oz^b^WgMeM{2@U)~>n;CW7!dE=d>?3o=;6&L@j;esD|r*b w3pHCZdle0juo^4&NjwTk2WIrxO8*nW|3M3@bRmjBE&u=k07*qoM6N<$f^3d3;s5{u diff --git a/iOS/Resources/Assets.xcassets/1password.imageset/onepassword-navbar@3x.png b/iOS/Resources/Assets.xcassets/1password.imageset/onepassword-navbar@3x.png deleted file mode 100644 index 24b64962a76ed7669ad426768d4745145faee70b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2757 zcmV;$3OePxxBL2e z-n-qKnSFP=d-v{-11GtAXP%jP=9%w2^UUv6PK8`YM@LJYQdfA9$Ob2!?sJqnUn$k& zcwWqNoEF4r<}aZfXOg%n&+`U}>vvqYU!_uS&UxPPcsxE;32~93e6Fjj>q6IYwo&=3 zsrW{|(PfwBd2@jEM}YcW%JU8n4-fx!*}Tq@F?3K`TU*K+) zeQ0`m`tdWkl`yjNh6V~+(TOJPg5e!}V|GF13e(Ld(4hT%_ntg?G9E@bVSv)u+ueN+ zg6&42rXqzJ6MNw)HK082Oxkh8RwVdqrn|+Uw?bbJ&pv2ti{XjZBmia)!0aLYY%zKA zrCAXuJv}|wBK)HWzdm09LPyvEK1UGzXeyODhQT~pD7_xn-rn9Di9|NJuDbP4h;=`o%m$FH2LTygVNdAxt^9gOvaA}F-0rl2)uW8Zf^hS z)2Anb(u6@RY#2gFr`M1yUhG6#|GQoBHanL=Yei^lxcjtdGG1xjyc=T-oDKCmSm=K!s6 z2h2AF8JC?NeHNE^eh-(}Dc3jCYSq*ndRkKUjGZ$@sT>jBIqOgtaR zCwCwyGE)tlm|;U|T|?}O(oLSHaqqS1&~`F_eA1>%I0%$Rf9h_f#&8`d(WqJ+suvt& zJ>;d+PvuAuiR_2gVAFFzSzO%NYnkJV!>*lw)r4u#FVc3!?rYgEiGTsX<1NY$BYCrGtD|sjmuglOLzx(Sg77dHOBO+U_*DbwY9Zo zXjxGFEkt|AjMZ)>7k3$<%K7zI?v}P-GFlt!>%WjGHwy|{vOPoezwC#|^53AnLjqHY z#6Eh#e~s$6Zg#!ogCe``BrrCau_aH3ydRQ+=Mh7YDg`DcCQhRSKh=vTd@&Q&4++f& zg@4=h1mc$+Gn=;3FvI!{0Q2jW@(Ck9Q{0^TWpLo@(52MXdO@hxK$u&I8t)l^`AjVU z!&2q<$n~}+o5P0rRT6e(=L#V=>X8gvzcIq$25JQu&#ot3=(1ipQIMldG{rc%1F;vw z#UY2A4-7)RniEq&0$u2+Ra||ltLtM?&UP;VA;ZZrgh41nfa&#S*nYSStV>~;Q_qOw zY;ZXv`GCb`tdw&ZD{oOjS%uRO&yhH1F;*0VkP=~6-okVw40asAxmelr4WgX%m(kS< zAd6M>Xq3gzFHR!=4Oi!BD--13py?8lqQk;ZizN)mJFQwAtlWMfZL&#*#2<<}M&HLG_KCjPZDy}k(-oi@}5l#K4Ar>>&kZn)4y!G_9xJKasnkZgcL zAIomG=qan{pJs7-eYwiVrW%)m7>%Y}{;`j= zyX>I)WpMC8@sAT7=XJecwB;uK)J%`7v>#x$hFNAT7CVQo@;RL+;ny zy3>f@G;}7Jd?G{3f-*inK8ChDmWhxrYTFK?-_&WV-H-Nvn>v{59bSQqjg4hDo3fxt zWzm#}`4}rw66|7V{Gi0IcE^Z%5tTGd0*a7OQ&W5Ox*4Ega7VbJ{r9!0?5P+k=4VpsRv61or&Z^Ay1MQKkk97`o=WY2Hfz&!G({4C-mmCJkJ^dR zpa%fv+8lZf6MDP4Za~v>Zew9f_Hq3+@XS0A6saTK=xb#BlU`rxkJy)I7^W}K>9y+T z!PH04`lpZ^MOktTC?{xm`@&;3L0(WuG0U0qHh?jVkWA{BBEMAY8biZf&pG)q@My`_ z77u{P-F&%^lho? zZxTqk>?gC-i2(&h2#@t<6V zZfa_}3+>nm+qNLqXh`{An`#9&FvER_7LdtLphF;eZvTv$pFcDhkB7Aht$Lg@jkYA@Jzxk~=617C|Ar=Lm=;6Fo)%RY{*&YKxZV*uSsOUub!A8|#U zZIiw>e(OL-2J2tVU&VL~a35y$i|aBi*H2@G>niyBDX*g)PuBGBrvyRk&mI2%OFKc( zEYRd;cT}k>F_;^m^kERiUSxigN$iH)sG3G~ Date: Sat, 22 Jan 2022 06:50:28 +0800 Subject: [PATCH 15/22] migrates to modern localization --- NetNewsWire.xcodeproj/project.pbxproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 368e8e991..585712c40 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -3237,10 +3237,9 @@ }; buildConfigurationList = 849C645B1ED37A5D003D8FC0 /* Build configuration list for PBXProject "NetNewsWire" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( - English, en, Base, ); From bf72a6e0ffbc20481c1251fc691a6818ae175de3 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sat, 22 Jan 2022 07:08:00 +0800 Subject: [PATCH 16/22] Suppress linker warnings from Account Package. --- Account/Package.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Account/Package.swift b/Account/Package.swift index 040712530..5192d8056 100644 --- a/Account/Package.swift +++ b/Account/Package.swift @@ -32,7 +32,10 @@ let package = Package( "ArticlesDatabase", "Secrets", "SyncDatabase", - ]), + ], + linkerSettings: [ + .unsafeFlags(["-Xlinker", "-no_application_extension"]) + ]), .testTarget( name: "AccountTests", dependencies: ["Account"], From ce46998d85be48b7d7960ba968813eddfac04969 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sat, 22 Jan 2022 07:39:02 +0800 Subject: [PATCH 17/22] Embed and Sign Account in Intents --- NetNewsWire.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 585712c40..49319ed89 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -849,6 +849,7 @@ D5F4EDB720074D6500B9E363 /* WebFeed+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB620074D6500B9E363 /* WebFeed+Scriptability.swift */; }; D5F4EDB920074D7C00B9E363 /* Folder+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */; }; DD82AB0A231003F6002269DF /* SharingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD82AB09231003F6002269DF /* SharingTests.swift */; }; + DFFB8FC2279B75E300AC21D7 /* Account in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51BC2F4A24D343A500E90810 /* Account */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; FF3ABF13232599810074C542 /* ArticleSorterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF09232599450074C542 /* ArticleSorterTests.swift */; }; FF3ABF1523259DDB0074C542 /* ArticleSorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */; }; FF3ABF162325AF5D0074C542 /* ArticleSorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */; }; @@ -963,6 +964,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + DFFB8FC2279B75E300AC21D7 /* Account in Embed Frameworks */, 513F32892593EF8F0003048F /* RSCore in Embed Frameworks */, 51BC2F4E24D343AB00E90810 /* RSTree in Embed Frameworks */, ); From 3aec411ec2a6ac642408bc409237cb14fd8a9aac Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sat, 22 Jan 2022 08:38:25 +0800 Subject: [PATCH 18/22] removes build settings from pbxproj --- NetNewsWire.xcodeproj/project.pbxproj | 8 -------- xcconfig/NetNewsWire_iOSwidgetextension_target.xcconfig | 2 -- xcconfig/NetNewsWire_project.xcconfig | 4 ++-- xcconfig/common/NetNewsWire_ios_target_common.xcconfig | 1 + xcconfig/common/NetNewsWire_mac_target_common.xcconfig | 2 +- 5 files changed, 4 insertions(+), 13 deletions(-) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 49319ed89..71c55e9a3 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -4615,7 +4615,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 1768140A2564BB8300D98635 /* NetNewsWire_iOSwidgetextension_target.xcconfig */; buildSettings = { - IPHONEOS_DEPLOYMENT_TARGET = 15.0; }; name = Debug; }; @@ -4623,7 +4622,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 1768140A2564BB8300D98635 /* NetNewsWire_iOSwidgetextension_target.xcconfig */; buildSettings = { - IPHONEOS_DEPLOYMENT_TARGET = 15.0; }; name = Release; }; @@ -4645,7 +4643,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 51314617235A797400387FDC /* NetNewsWire_iOSintentextension_target.xcconfig */; buildSettings = { - IPHONEOS_DEPLOYMENT_TARGET = 15.0; }; name = Debug; }; @@ -4653,7 +4650,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 51314617235A797400387FDC /* NetNewsWire_iOSintentextension_target.xcconfig */; buildSettings = { - IPHONEOS_DEPLOYMENT_TARGET = 15.0; }; name = Release; }; @@ -4661,7 +4657,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 515D4FCE2325B3D000EE1167 /* NetNewsWire_iOSshareextension_target.xcconfig */; buildSettings = { - IPHONEOS_DEPLOYMENT_TARGET = 15.0; }; name = Debug; }; @@ -4669,7 +4664,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 515D4FCE2325B3D000EE1167 /* NetNewsWire_iOSshareextension_target.xcconfig */; buildSettings = { - IPHONEOS_DEPLOYMENT_TARGET = 15.0; }; name = Release; }; @@ -4747,7 +4741,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */; buildSettings = { - IPHONEOS_DEPLOYMENT_TARGET = 15.0; }; name = Debug; }; @@ -4755,7 +4748,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */; buildSettings = { - IPHONEOS_DEPLOYMENT_TARGET = 15.0; }; name = Release; }; diff --git a/xcconfig/NetNewsWire_iOSwidgetextension_target.xcconfig b/xcconfig/NetNewsWire_iOSwidgetextension_target.xcconfig index d495b1cc5..c79806d19 100644 --- a/xcconfig/NetNewsWire_iOSwidgetextension_target.xcconfig +++ b/xcconfig/NetNewsWire_iOSwidgetextension_target.xcconfig @@ -39,5 +39,3 @@ CODE_SIGN_ENTITLEMENTS = Widget/NetNewsWire_iOS_WidgetExtension.entitlements INFOPLIST_FILE = Widget/Info.plist PRODUCT_BUNDLE_IDENTIFIER = $(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS.SpringboardWidgets PRODUCT_NAME = $(TARGET_NAME) - -IPHONEOS_DEPLOYMENT_TARGET = 14.0 diff --git a/xcconfig/NetNewsWire_project.xcconfig b/xcconfig/NetNewsWire_project.xcconfig index 465091c17..67951fe33 100644 --- a/xcconfig/NetNewsWire_project.xcconfig +++ b/xcconfig/NetNewsWire_project.xcconfig @@ -39,8 +39,8 @@ GCC_WARN_UNDECLARED_SELECTOR = YES GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE GCC_WARN_UNUSED_FUNCTION = YES GCC_WARN_UNUSED_VARIABLE = YES -MACOSX_DEPLOYMENT_TARGET = 10.15 -IPHONEOS_DEPLOYMENT_TARGET = 13.0 +MACOSX_DEPLOYMENT_TARGET = 11.0 +IPHONEOS_DEPLOYMENT_TARGET = 15.0 //SDKROOT = macosx SWIFT_SWIFT3_OBJC_INFERENCE = Off SWIFT_VERSION = 5.1 diff --git a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig index 3d6d7a3de..f4fdf4d29 100644 --- a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig +++ b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig @@ -10,3 +10,4 @@ COMBINE_HIDPI_IMAGES = YES GCC_C_LANGUAGE_STANDARD = gnu11; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = 1,2 +IPHONEOS_DEPLOYMENT_TARGET = 15.0 diff --git a/xcconfig/common/NetNewsWire_mac_target_common.xcconfig b/xcconfig/common/NetNewsWire_mac_target_common.xcconfig index ae98de640..7c590ee73 100644 --- a/xcconfig/common/NetNewsWire_mac_target_common.xcconfig +++ b/xcconfig/common/NetNewsWire_mac_target_common.xcconfig @@ -6,5 +6,5 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon COMBINE_HIDPI_IMAGES = YES -MACOSX_DEPLOYMENT_TARGET = 10.15 +MACOSX_DEPLOYMENT_TARGET = 11.0 SDKROOT = macosx; From 98da0a534ba8818c10dd08b8db67c7bbf4b6ff9b Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sat, 22 Jan 2022 08:38:56 +0800 Subject: [PATCH 19/22] Mac Notifications: switches alert for banner --- Mac/AppDelegate.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index ad024b046..86692df48 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -497,7 +497,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, // MARK: UNUserNotificationCenterDelegate func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { - completionHandler([.alert, .badge, .sound]) + completionHandler([.banner, .badge, .sound]) } func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { From d680fe0988e4382905f6510c28a68652d8cc93a0 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sat, 22 Jan 2022 08:39:57 +0800 Subject: [PATCH 20/22] WKWebpagePreferences added to macOS --- Mac/MainWindow/Detail/DetailWebViewController.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index e7ca8045d..664d4c723 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -83,9 +83,11 @@ final class DetailWebViewController: NSViewController { let preferences = WKPreferences() preferences.minimumFontSize = 12.0 preferences.javaScriptCanOpenWindowsAutomatically = false - preferences.javaScriptEnabled = true - + + let webpagePrefs = WKWebpagePreferences() + let configuration = WKWebViewConfiguration() + configuration.defaultWebpagePreferences = webpagePrefs configuration.preferences = preferences configuration.setURLSchemeHandler(detailIconSchemeHandler, forURLScheme: ArticleRenderer.imageIconScheme) From 7a670516fb3aae3f856958510ec7a53978921189 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sat, 22 Jan 2022 20:42:24 +0800 Subject: [PATCH 21/22] if #available Removes < iOS 15 if #available checks --- iOS/AppDelegate.swift | 12 +- .../Cell/MasterFeedTableViewCell.swift | 4 +- .../MasterFeedTableViewSectionHeader.swift | 4 +- iOS/MasterFeed/MasterFeedViewController.swift | 146 ++++++------------ .../MasterTimelineTitleView.swift | 8 +- .../MasterTimelineViewController.swift | 15 +- iOS/SceneDelegate.swift | 4 +- 7 files changed, 55 insertions(+), 138 deletions(-) diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift index e8cbd8832..f30a509a7 100644 --- a/iOS/AppDelegate.swift +++ b/iOS/AppDelegate.swift @@ -398,9 +398,7 @@ private extension AppDelegate { } AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) { [unowned self] in if !AccountManager.shared.isSuspended { - if #available(iOS 14, *) { - try? WidgetDataEncoder.shared.encodeWidgetData() - } + try? WidgetDataEncoder.shared.encodeWidgetData() self.suspendApplication() os_log("Account refresh operation completed.", log: self.log, type: .info) task.setTaskCompleted(success: true) @@ -445,9 +443,7 @@ private extension AppDelegate { self.prepareAccountsForBackground() account!.syncArticleStatus(completion: { [weak self] _ in if !AccountManager.shared.isSuspended { - if #available(iOS 14, *) { - try? WidgetDataEncoder.shared.encodeWidgetData() - } + try? WidgetDataEncoder.shared.encodeWidgetData() self?.prepareAccountsForBackground() self?.suspendApplication() } @@ -474,9 +470,7 @@ private extension AppDelegate { account!.markArticles(article!, statusKey: .starred, flag: true) { _ in } account!.syncArticleStatus(completion: { [weak self] _ in if !AccountManager.shared.isSuspended { - if #available(iOS 14, *) { - try? WidgetDataEncoder.shared.encodeWidgetData() - } + try? WidgetDataEncoder.shared.encodeWidgetData() self?.prepareAccountsForBackground() self?.suspendApplication() } diff --git a/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift b/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift index 4cb6f90e6..bf9c5a8da 100644 --- a/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift +++ b/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift @@ -197,9 +197,7 @@ private extension MasterFeedTableViewCell { disclosureButton?.tintColor = AppAssets.controlBackgroundColor disclosureButton?.imageView?.contentMode = .center disclosureButton?.imageView?.clipsToBounds = false - if #available(iOS 13.4, *) { - disclosureButton?.addInteraction(UIPointerInteraction()) - } + disclosureButton?.addInteraction(UIPointerInteraction()) addSubviewAtInit(disclosureButton!) } diff --git a/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift b/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift index dc5b21ed6..ee9cc296b 100644 --- a/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift +++ b/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift @@ -88,9 +88,7 @@ class MasterFeedTableViewSectionHeader: UITableViewHeaderFooterView { button.tintColor = UIColor.tertiaryLabel button.setImage(AppAssets.disclosureImage, for: .normal) button.contentMode = .center - if #available(iOS 13.4, *) { - button.addInteraction(UIPointerInteraction()) - } + button.addInteraction(UIPointerInteraction()) button.addTarget(self, action: #selector(toggleDisclosure), for: .touchUpInside) return button }() diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 6e2964c2d..c61d6d329 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -19,11 +19,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { private var refreshProgressView: RefreshProgressView? @IBOutlet weak var addNewItemButton: UIBarButtonItem! { didSet { - if #available(iOS 14, *) { - addNewItemButton.primaryAction = nil - } else { - addNewItemButton.action = #selector(MasterFeedViewController.add(_:)) - } + addNewItemButton.primaryAction = nil } } @@ -429,59 +425,6 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { coordinator.toggleReadFeedsFilter() } - @IBAction func add(_ sender: UIBarButtonItem) { - - if #available(iOS 14, *) { - - } else { - let title = NSLocalizedString("Add Item", comment: "Add Item") - let alertController = UIAlertController(title: title, message: nil, preferredStyle: .actionSheet) - - let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel") - let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel) - - let addWebFeedActionTitle = NSLocalizedString("Add Web Feed", comment: "Add Web Feed") - let addWebFeedAction = UIAlertAction(title: addWebFeedActionTitle, style: .default) { _ in - self.coordinator.showAddWebFeed() - } - - let addRedditFeedActionTitle = NSLocalizedString("Add Reddit Feed", comment: "Add Reddit Feed") - let addRedditFeedAction = UIAlertAction(title: addRedditFeedActionTitle, style: .default) { _ in - self.coordinator.showAddRedditFeed() - } - - let addTwitterFeedActionTitle = NSLocalizedString("Add Twitter Feed", comment: "Add Twitter Feed") - let addTwitterFeedAction = UIAlertAction(title: addTwitterFeedActionTitle, style: .default) { _ in - self.coordinator.showAddTwitterFeed() - } - - let addWebFolderdActionTitle = NSLocalizedString("Add Folder", comment: "Add Folder") - let addWebFolderAction = UIAlertAction(title: addWebFolderdActionTitle, style: .default) { _ in - self.coordinator.showAddFolder() - } - - alertController.addAction(addWebFeedAction) - - if AccountManager.shared.activeAccounts.contains(where: { $0.type == .onMyMac || $0.type == .cloudKit }) { - if ExtensionPointManager.shared.isRedditEnabled { - alertController.addAction(addRedditFeedAction) - } - if ExtensionPointManager.shared.isTwitterEnabled { - alertController.addAction(addTwitterFeedAction) - } - } - - alertController.addAction(addWebFolderAction) - alertController.addAction(cancelAction) - - alertController.popoverPresentationController?.barButtonItem = sender - - present(alertController, animated: true) - } - - - } - @objc func toggleSectionHeader(_ sender: UITapGestureRecognizer) { guard let headerView = sender.view as? MasterFeedTableViewSectionHeader else { return @@ -644,52 +587,49 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { @objc func configureContextMenu(_: Any? = nil) { - if #available(iOS 14.0, *) { - - /* - Context Menu Order: - 1. Add Web Feed - 2. Add Reddit Feed - 3. Add Twitter Feed - 4. Add Folder - */ - - var menuItems: [UIAction] = [] - - let addWebFeedActionTitle = NSLocalizedString("Add Web Feed", comment: "Add Web Feed") - let addWebFeedAction = UIAction(title: addWebFeedActionTitle, image: AppAssets.plus) { _ in - self.coordinator.showAddWebFeed() - } - menuItems.append(addWebFeedAction) - - if AccountManager.shared.activeAccounts.contains(where: { $0.type == .onMyMac || $0.type == .cloudKit }) { - if ExtensionPointManager.shared.isRedditEnabled { - let addRedditFeedActionTitle = NSLocalizedString("Add Reddit Feed", comment: "Add Reddit Feed") - let addRedditFeedAction = UIAction(title: addRedditFeedActionTitle, image: AppAssets.contextMenuReddit.tinted(color: .label)) { _ in - self.coordinator.showAddRedditFeed() - } - menuItems.append(addRedditFeedAction) - } - if ExtensionPointManager.shared.isTwitterEnabled { - let addTwitterFeedActionTitle = NSLocalizedString("Add Twitter Feed", comment: "Add Twitter Feed") - let addTwitterFeedAction = UIAction(title: addTwitterFeedActionTitle, image: AppAssets.contextMenuTwitter.tinted(color: .label)) { _ in - self.coordinator.showAddTwitterFeed() - } - menuItems.append(addTwitterFeedAction) - } - } - - let addWebFolderActionTitle = NSLocalizedString("Add Folder", comment: "Add Folder") - let addWebFolderAction = UIAction(title: addWebFolderActionTitle, image: AppAssets.folderOutlinePlus) { _ in - self.coordinator.showAddFolder() - } - - menuItems.append(addWebFolderAction) - - let contextMenu = UIMenu(title: NSLocalizedString("Add Item", comment: "Add Item"), image: nil, identifier: nil, options: [], children: menuItems.reversed()) - - self.addNewItemButton.menu = contextMenu + /* + Context Menu Order: + 1. Add Web Feed + 2. Add Reddit Feed + 3. Add Twitter Feed + 4. Add Folder + */ + + var menuItems: [UIAction] = [] + + let addWebFeedActionTitle = NSLocalizedString("Add Web Feed", comment: "Add Web Feed") + let addWebFeedAction = UIAction(title: addWebFeedActionTitle, image: AppAssets.plus) { _ in + self.coordinator.showAddWebFeed() } + menuItems.append(addWebFeedAction) + + if AccountManager.shared.activeAccounts.contains(where: { $0.type == .onMyMac || $0.type == .cloudKit }) { + if ExtensionPointManager.shared.isRedditEnabled { + let addRedditFeedActionTitle = NSLocalizedString("Add Reddit Feed", comment: "Add Reddit Feed") + let addRedditFeedAction = UIAction(title: addRedditFeedActionTitle, image: AppAssets.contextMenuReddit.tinted(color: .label)) { _ in + self.coordinator.showAddRedditFeed() + } + menuItems.append(addRedditFeedAction) + } + if ExtensionPointManager.shared.isTwitterEnabled { + let addTwitterFeedActionTitle = NSLocalizedString("Add Twitter Feed", comment: "Add Twitter Feed") + let addTwitterFeedAction = UIAction(title: addTwitterFeedActionTitle, image: AppAssets.contextMenuTwitter.tinted(color: .label)) { _ in + self.coordinator.showAddTwitterFeed() + } + menuItems.append(addTwitterFeedAction) + } + } + + let addWebFolderActionTitle = NSLocalizedString("Add Folder", comment: "Add Folder") + let addWebFolderAction = UIAction(title: addWebFolderActionTitle, image: AppAssets.folderOutlinePlus) { _ in + self.coordinator.showAddFolder() + } + + menuItems.append(addWebFolderAction) + + let contextMenu = UIMenu(title: NSLocalizedString("Add Item", comment: "Add Item"), image: nil, identifier: nil, options: [], children: menuItems.reversed()) + + self.addNewItemButton.menu = contextMenu } func focus() { diff --git a/iOS/MasterTimeline/MasterTimelineTitleView.swift b/iOS/MasterTimeline/MasterTimelineTitleView.swift index 9c81ac7ee..dce1f4ea7 100644 --- a/iOS/MasterTimeline/MasterTimelineTitleView.swift +++ b/iOS/MasterTimeline/MasterTimelineTitleView.swift @@ -35,17 +35,13 @@ class MasterTimelineTitleView: UIView { func buttonize() { heightAnchor.constraint(equalToConstant: 40.0).isActive = true accessibilityTraits = .button - if #available(iOS 13.4, *) { - addInteraction(pointerInteraction) - } + addInteraction(pointerInteraction) } func debuttonize() { heightAnchor.constraint(equalToConstant: 40.0).isActive = true accessibilityTraits.remove(.button) - if #available(iOS 13.4, *) { - removeInteraction(pointerInteraction) - } + removeInteraction(pointerInteraction) } } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 9456f369b..238915f03 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -101,12 +101,10 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner } // Disable swipe back on iPad Mice - if #available(iOS 13.4, *) { - guard let gesture = self.navigationController?.interactivePopGestureRecognizer as? UIPanGestureRecognizer else { - return - } - gesture.allowedScrollTypesMask = [] + guard let gesture = self.navigationController?.interactivePopGestureRecognizer as? UIPanGestureRecognizer else { + return } + gesture.allowedScrollTypesMask = [] } @@ -525,12 +523,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner } @objc private func reloadAllVisibleCells() { - if #available(iOS 15, *) { - reconfigureCells(coordinator.articles) - } else { - let visibleArticles = tableView.indexPathsForVisibleRows!.compactMap { return dataSource.itemIdentifier(for: $0) } - reloadCells(visibleArticles) - } + reconfigureCells(coordinator.articles) } private func reloadCells(_ articles: [Article]) { diff --git a/iOS/SceneDelegate.swift b/iOS/SceneDelegate.swift index 523446eed..f054d9846 100644 --- a/iOS/SceneDelegate.swift +++ b/iOS/SceneDelegate.swift @@ -66,9 +66,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } func sceneDidEnterBackground(_ scene: UIScene) { - if #available(iOS 14, *) { - try? WidgetDataEncoder.shared.encodeWidgetData() - } + try? WidgetDataEncoder.shared.encodeWidgetData() ArticleStringFormatter.emptyCaches() appDelegate.prepareAccountsForBackground() } From b08d9dfe73d8a470e2f2fb8b7397708e1b1f1bd6 Mon Sep 17 00:00:00 2001 From: Stuart Breckenridge Date: Sat, 22 Jan 2022 20:55:09 +0800 Subject: [PATCH 22/22] if #available mac Removes if #available for anything less than macOS 11 --- Mac/AppAssets.swift | 118 ++--- Mac/AppDelegate.swift | 26 +- ...ltinSmartFeedInspectorViewController.swift | 4 +- .../FolderInspectorViewController.swift | 8 +- Mac/MainWindow/Detail/DetailWebView.swift | 36 +- .../Detail/DetailWebViewController.swift | 19 +- Mac/MainWindow/MainWindowController.swift | 470 +++++++----------- .../Sidebar/Cell/SidebarCellAppearance.swift | 6 +- .../Cell/TimelineCellAppearance.swift | 6 +- .../Timeline/TimelineTableRowView.swift | 21 +- .../Timeline/TimelineViewController.swift | 5 +- ...AccountsFeedWranglerWindowController.swift | 6 +- .../AccountsFeedbinWindowController.swift | 6 +- .../AccountsNewsBlurWindowController.swift | 6 +- .../AccountsReaderAPIWindowController.swift | 6 +- .../Accounts/AddAccountsView.swift | 58 +-- .../EnableExtensionPointView.swift | 59 +-- 17 files changed, 288 insertions(+), 572 deletions(-) diff --git a/Mac/AppAssets.swift b/Mac/AppAssets.swift index 74c69df40..fa6851b01 100644 --- a/Mac/AppAssets.swift +++ b/Mac/AppAssets.swift @@ -100,19 +100,11 @@ struct AppAssets { }() static var filterActive: RSImage = { - if #available(macOS 11.0, *) { - return NSImage(systemSymbolName: "line.horizontal.3.decrease.circle.fill", accessibilityDescription: nil)! - } else { - return RSImage(named: "filterActive")! - } + return NSImage(systemSymbolName: "line.horizontal.3.decrease.circle.fill", accessibilityDescription: nil)! }() static var filterInactive: RSImage = { - if #available(macOS 11.0, *) { - return NSImage(systemSymbolName: "line.horizontal.3.decrease.circle", accessibilityDescription: nil)! - } else { - return RSImage(named: "filterInactive")! - } + return NSImage(systemSymbolName: "line.horizontal.3.decrease.circle", accessibilityDescription: nil)! }() static var iconLightBackgroundColor: NSColor = { @@ -156,14 +148,10 @@ struct AppAssets { }() static var masterFolderImage: IconImage { - if #available(macOS 11.0, *) { - let image = NSImage(systemSymbolName: "folder", accessibilityDescription: nil)! - let preferredColor = NSColor(named: "AccentColor")! - let coloredImage = image.tinted(with: preferredColor) - return IconImage(coloredImage, isSymbol: true, isBackgroundSupressed: true, preferredColor: preferredColor.cgColor) - } else { - return IconImage(RSImage(named: NSImage.folderName)!) - } + let image = NSImage(systemSymbolName: "folder", accessibilityDescription: nil)! + let preferredColor = NSColor(named: "AccentColor")! + let coloredImage = image.tinted(with: preferredColor) + return IconImage(coloredImage, isSymbol: true, isBackgroundSupressed: true, preferredColor: preferredColor.cgColor) } static var markAllAsReadImage: RSImage = { @@ -181,35 +169,19 @@ struct AppAssets { }() static var preferencesToolbarAccountsImage: RSImage = { - if #available(macOS 11.0, *) { - return NSImage(systemSymbolName: "at", accessibilityDescription: nil)! - } else { - return NSImage(named: NSImage.userAccountsName)! - } + return NSImage(systemSymbolName: "at", accessibilityDescription: nil)! }() static var preferencesToolbarExtensionsImage: RSImage = { - if #available(macOS 11.0, *) { - return RSImage(named: "preferencesToolbarExtensions")! - } else { - return NSImage(contentsOfFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/KEXT.icns")! - } + return RSImage(named: "preferencesToolbarExtensions")! }() static var preferencesToolbarGeneralImage: RSImage = { - if #available(macOS 11.0, *) { - return NSImage(systemSymbolName: "gearshape", accessibilityDescription: nil)! - } else { - return NSImage(named: NSImage.preferencesGeneralName)! - } + return NSImage(systemSymbolName: "gearshape", accessibilityDescription: nil)! }() static var preferencesToolbarAdvancedImage: RSImage = { - if #available(macOS 11.0, *) { - return NSImage(systemSymbolName: "gearshape.2", accessibilityDescription: nil)! - } else { - return NSImage(named: NSImage.advancedName)! - } + return NSImage(systemSymbolName: "gearshape.2", accessibilityDescription: nil)! }() @available(macOS 11.0, *) @@ -252,14 +224,10 @@ struct AppAssets { }() static var starredFeedImage: IconImage = { - if #available(macOS 11.0, *) { - let image = NSImage(systemSymbolName: "star.fill", accessibilityDescription: nil)! - let preferredColor = NSColor(named: "StarColor")! - let coloredImage = image.tinted(with: preferredColor) - return IconImage(coloredImage, isSymbol: true, isBackgroundSupressed: true, preferredColor: preferredColor.cgColor) - } else { - return IconImage(RSImage(named: NSImage.smartBadgeTemplateName)!, isBackgroundSupressed: true) - } + let image = NSImage(systemSymbolName: "star.fill", accessibilityDescription: nil)! + let preferredColor = NSColor(named: "StarColor")! + let coloredImage = image.tinted(with: preferredColor) + return IconImage(coloredImage, isSymbol: true, isBackgroundSupressed: true, preferredColor: preferredColor.cgColor) }() static var timelineSeparatorColor: NSColor = { @@ -275,63 +243,37 @@ struct AppAssets { }() static var todayFeedImage: IconImage = { - if #available(macOS 11.0, *) { - let image = NSImage(systemSymbolName: "sun.max.fill", accessibilityDescription: nil)! - let preferredColor = NSColor.orange - let coloredImage = image.tinted(with: preferredColor) - return IconImage(coloredImage, isSymbol: true, isBackgroundSupressed: true, preferredColor: preferredColor.cgColor) - } else { - return IconImage(RSImage(named: NSImage.smartBadgeTemplateName)!, isBackgroundSupressed: true) - } + let image = NSImage(systemSymbolName: "sun.max.fill", accessibilityDescription: nil)! + let preferredColor = NSColor.orange + let coloredImage = image.tinted(with: preferredColor) + return IconImage(coloredImage, isSymbol: true, isBackgroundSupressed: true, preferredColor: preferredColor.cgColor) }() static var unreadFeedImage: IconImage = { - if #available(macOS 11.0, *) { - let image = NSImage(systemSymbolName: "largecircle.fill.circle", accessibilityDescription: nil)! - let preferredColor = NSColor(named: "AccentColor")! - let coloredImage = image.tinted(with: preferredColor) - return IconImage(coloredImage, isSymbol: true, isBackgroundSupressed: true, preferredColor: preferredColor.cgColor) - } else { - return IconImage(RSImage(named: NSImage.smartBadgeTemplateName)!, isBackgroundSupressed: true) - } + let image = NSImage(systemSymbolName: "largecircle.fill.circle", accessibilityDescription: nil)! + let preferredColor = NSColor(named: "AccentColor")! + let coloredImage = image.tinted(with: preferredColor) + return IconImage(coloredImage, isSymbol: true, isBackgroundSupressed: true, preferredColor: preferredColor.cgColor) }() static var swipeMarkReadImage: RSImage = { - if #available(OSX 11.0, *) { - return RSImage(systemSymbolName: "circle", accessibilityDescription: "Mark Read")! - .withSymbolConfiguration(.init(scale: .large))! - } else { - // TODO: remove swipeMarkRead asset when dropping support for macOS 10.15 - return RSImage(named: "swipeMarkRead")! - } + return RSImage(systemSymbolName: "circle", accessibilityDescription: "Mark Read")! + .withSymbolConfiguration(.init(scale: .large))! }() static var swipeMarkUnreadImage: RSImage = { - if #available(OSX 11.0, *) { - return RSImage(systemSymbolName: "largecircle.fill.circle", accessibilityDescription: "Mark Unread")! - .withSymbolConfiguration(.init(scale: .large))! - } else { - // TODO: remove swipeMarkUnread asset when dropping support for macOS 10.15 - return RSImage(named: "swipeMarkUnread")! - } + return RSImage(systemSymbolName: "largecircle.fill.circle", accessibilityDescription: "Mark Unread")! + .withSymbolConfiguration(.init(scale: .large))! }() static var swipeMarkStarredImage: RSImage = { - if #available(OSX 11.0, *) { - return RSImage(systemSymbolName: "star.fill", accessibilityDescription: "Star")! - .withSymbolConfiguration(.init(scale: .large))! - } else { - return RSImage(named: "swipeMarkStarred")! - } + return RSImage(systemSymbolName: "star.fill", accessibilityDescription: "Star")! + .withSymbolConfiguration(.init(scale: .large))! }() static var swipeMarkUnstarredImage: RSImage = { - if #available(OSX 11.0, *) { - return RSImage(systemSymbolName: "star", accessibilityDescription: "Unstar")! - .withSymbolConfiguration(.init(scale: .large))! - } else { - return RSImage(named: "swipeMarkUnstarred")! - } + return RSImage(systemSymbolName: "star", accessibilityDescription: "Unstar")! + .withSymbolConfiguration(.init(scale: .large))! }() static var starColor: NSColor = { diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 86692df48..3307734d8 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -396,11 +396,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, func createMainWindowController() -> MainWindowController { let controller: MainWindowController - if #available(macOS 11.0, *) { - controller = windowControllerWithName("UnifiedWindow") as! MainWindowController - } else { - controller = windowControllerWithName("MainWindow") as! MainWindowController - } + controller = windowControllerWithName("UnifiedWindow") as! MainWindowController if !(mainWindowController?.isOpen ?? false) { mainWindowControllers.removeAll() @@ -828,30 +824,20 @@ internal extension AppDelegate { attrs[.font] = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize) attrs[.foregroundColor] = NSColor.textColor - if #available(macOS 11.0, *) { - let titleParagraphStyle = NSMutableParagraphStyle() - titleParagraphStyle.alignment = .center - attrs[.paragraphStyle] = titleParagraphStyle - } + let titleParagraphStyle = NSMutableParagraphStyle() + titleParagraphStyle.alignment = .center + attrs[.paragraphStyle] = titleParagraphStyle let websiteText = NSMutableAttributedString() websiteText.append(NSAttributedString(string: NSLocalizedString("Author's Website", comment: "Author's Website"), attributes: attrs)) - if #available(macOS 11.0, *) { - websiteText.append(NSAttributedString(string: "\n")) - } else { - websiteText.append(NSAttributedString(string: " ")) - } + websiteText.append(NSAttributedString(string: "\n")) attrs[.link] = theme.creatorHomePage websiteText.append(NSAttributedString(string: theme.creatorHomePage, attributes: attrs)) let textViewWidth: CGFloat - if #available(macOS 11.0, *) { - textViewWidth = 200 - } else { - textViewWidth = 400 - } + textViewWidth = 200 let textView = NSTextView(frame: CGRect(x: 0, y: 0, width: textViewWidth, height: 15)) textView.isEditable = false diff --git a/Mac/Inspector/BuiltinSmartFeedInspectorViewController.swift b/Mac/Inspector/BuiltinSmartFeedInspectorViewController.swift index bbd1a928d..3c6c7b908 100644 --- a/Mac/Inspector/BuiltinSmartFeedInspectorViewController.swift +++ b/Mac/Inspector/BuiltinSmartFeedInspectorViewController.swift @@ -64,8 +64,6 @@ private extension BuiltinSmartFeedInspectorViewController { func updateUI() { nameTextField?.stringValue = smartFeed?.nameForDisplay ?? "" windowTitle = smartFeed?.nameForDisplay ?? NSLocalizedString("Smart Feed Inspector", comment: "Smart Feed Inspector window title") - if #available(macOS 11.0, *) { - smartFeedImageView?.image = smartFeed?.smallIcon?.image - } + smartFeedImageView?.image = smartFeed?.smallIcon?.image } } diff --git a/Mac/Inspector/FolderInspectorViewController.swift b/Mac/Inspector/FolderInspectorViewController.swift index d67dfca80..63f76b0d7 100644 --- a/Mac/Inspector/FolderInspectorViewController.swift +++ b/Mac/Inspector/FolderInspectorViewController.swift @@ -47,11 +47,9 @@ final class FolderInspectorViewController: NSViewController, Inspector { override func viewDidLoad() { updateUI() - if #available(macOS 11.0, *) { - let image = NSImage(systemSymbolName: "folder", accessibilityDescription: nil)! - folderImageView.image = image - folderImageView.contentTintColor = NSColor.controlAccentColor - } + let image = NSImage(systemSymbolName: "folder", accessibilityDescription: nil)! + folderImageView.image = image + folderImageView.contentTintColor = NSColor.controlAccentColor NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil) } diff --git a/Mac/MainWindow/Detail/DetailWebView.swift b/Mac/MainWindow/Detail/DetailWebView.swift index a6e4a5c45..3fd16cb5e 100644 --- a/Mac/MainWindow/Detail/DetailWebView.swift +++ b/Mac/MainWindow/Detail/DetailWebView.swift @@ -74,26 +74,24 @@ final class DetailWebView: WKWebView { This code adjusts the height of the window by -1pt/+1pt, which puts the webview back in the correct place. */ - if #available(macOS 11, *) { - guard var frame = window?.frame else { - return - } - - guard !inBigSurOffsetFix else { - return - } - - inBigSurOffsetFix = true - - defer { - inBigSurOffsetFix = false - } - - frame.size = NSSize(width: window!.frame.width, height: window!.frame.height - 1) - window!.setFrame(frame, display: false) - frame.size = NSSize(width: window!.frame.width, height: window!.frame.height + 1) - window!.setFrame(frame, display: false) + guard var frame = window?.frame else { + return } + + guard !inBigSurOffsetFix else { + return + } + + inBigSurOffsetFix = true + + defer { + inBigSurOffsetFix = false + } + + frame.size = NSSize(width: window!.frame.width, height: window!.frame.height - 1) + window!.setFrame(frame, display: false) + frame.size = NSSize(width: window!.frame.width, height: window!.frame.height + 1) + window!.setFrame(frame, display: false) } } diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index 664d4c723..0c3465f03 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -108,20 +108,6 @@ final class DetailWebViewController: NSViewController { view = webView - // Use the safe area layout guides if they are available. - if #available(OSX 11.0, *) { - // These constraints have been removed as they were unsatisfiable after removing NSBox. - } else { - let constraints = [ - webView.topAnchor.constraint(equalTo: view.topAnchor), - webView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - webView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - webView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - ] - NSLayoutConstraint.activate(constraints) - } - - // Hide the web view until the first reload (navigation) is complete (plus some delay) to avoid the awful white flash that happens on the initial display in dark mode. // See bug #901. webView.isHidden = true @@ -333,10 +319,7 @@ private extension DetailWebViewController { } func fetchScrollInfo(_ completion: @escaping (ScrollInfo?) -> Void) { - var javascriptString = "var x = {contentHeight: document.body.scrollHeight, offsetY: document.body.scrollTop}; x" - if #available(macOS 10.15, *) { - javascriptString = "var x = {contentHeight: document.body.scrollHeight, offsetY: window.pageYOffset}; x" - } + let javascriptString = "var x = {contentHeight: document.body.scrollHeight, offsetY: window.pageYOffset}; x" webView.evaluateJavaScript(javascriptString) { (info, error) in guard let info = info as? [String: Any] else { diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index 6eee441f9..cea30f75e 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -67,18 +67,12 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { updateArticleThemeMenu() - if #available(macOS 11.0, *) { - let toolbar = NSToolbar(identifier: "MainWindowToolbar") - toolbar.allowsUserCustomization = true - toolbar.autosavesConfiguration = true - toolbar.displayMode = .iconOnly - toolbar.delegate = self - self.window?.toolbar = toolbar - } else { - if !AppDefaults.shared.showTitleOnMainWindow { - window?.titleVisibility = .hidden - } - } + let toolbar = NSToolbar(identifier: "MainWindowToolbar") + toolbar.allowsUserCustomization = true + toolbar.autosavesConfiguration = true + toolbar.displayMode = .iconOnly + toolbar.delegate = self + self.window?.toolbar = toolbar if let window = window { let point = NSPoint(x: 128, y: 64) @@ -776,173 +770,130 @@ extension NSToolbarItem.Identifier { extension MainWindowController: NSToolbarDelegate { func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? { - if #available(macOS 11.0, *) { + switch itemIdentifier { - switch itemIdentifier { + case .sidebarToggle: + let title = NSLocalizedString("Toggle Sidebar", comment: "Toggle Sidebar") + return buildToolbarButton(.toggleSidebar, title, AppAssets.sidebarToggleImage, "toggleTheSidebar:") - case .sidebarToggle: - let title = NSLocalizedString("Toggle Sidebar", comment: "Toggle Sidebar") - return buildToolbarButton(.toggleSidebar, title, AppAssets.sidebarToggleImage, "toggleTheSidebar:") - - case .refresh: - let title = NSLocalizedString("Refresh", comment: "Refresh") - return buildToolbarButton(.refresh, title, AppAssets.refreshImage, "refreshAll:") - - case .newSidebarItemMenu: - let toolbarItem = NSMenuToolbarItem(itemIdentifier: .newSidebarItemMenu) - toolbarItem.image = AppAssets.addNewSidebarItemImage - let description = NSLocalizedString("Add Item", comment: "Add Item") - toolbarItem.toolTip = description - toolbarItem.label = description - toolbarItem.menu = buildNewSidebarItemMenu() - return toolbarItem - - case .markAllAsRead: - let title = NSLocalizedString("Mark All as Read", comment: "Mark All as Read") - return buildToolbarButton(.markAllAsRead, title, AppAssets.markAllAsReadImage, "markAllAsRead:") - - case .toggleReadArticlesFilter: - let title = NSLocalizedString("Read Articles Filter", comment: "Read Articles Filter") - return buildToolbarButton(.toggleReadArticlesFilter, title, AppAssets.filterInactive, "toggleReadArticlesFilter:") - - case .timelineTrackingSeparator: - return NSTrackingSeparatorToolbarItem(identifier: .timelineTrackingSeparator, splitView: splitViewController!.splitView, dividerIndex: 1) - - case .markRead: - let title = NSLocalizedString("Mark Read", comment: "Mark Read") - return buildToolbarButton(.markRead, title, AppAssets.readClosedImage, "toggleRead:") - - case .markStar: - let title = NSLocalizedString("Star", comment: "Star") - return buildToolbarButton(.markStar, title, AppAssets.starOpenImage, "toggleStarred:") - - case .nextUnread: - let title = NSLocalizedString("Next Unread", comment: "Next Unread") - return buildToolbarButton(.nextUnread, title, AppAssets.nextUnreadImage, "nextUnread:") - - case .readerView: - let toolbarItem = RSToolbarItem(itemIdentifier: .readerView) - toolbarItem.autovalidates = true - let description = NSLocalizedString("Reader View", comment: "Reader View") - toolbarItem.toolTip = description - toolbarItem.label = description - let button = ArticleExtractorButton() - button.action = #selector(toggleArticleExtractor(_:)) - toolbarItem.view = button - return toolbarItem - - case .share: - let title = NSLocalizedString("Share", comment: "Share") - return buildToolbarButton(.share, title, AppAssets.shareImage, "toolbarShowShareMenu:") - - case .openInBrowser: - let title = NSLocalizedString("Open in Browser", comment: "Open in Browser") - return buildToolbarButton(.openInBrowser, title, AppAssets.openInBrowserImage, "openArticleInBrowser:") - - case .articleThemeMenu: - articleThemeMenuToolbarItem.image = AppAssets.articleTheme - let description = NSLocalizedString("Article Theme", comment: "Article Theme") - articleThemeMenuToolbarItem.toolTip = description - articleThemeMenuToolbarItem.label = description - return articleThemeMenuToolbarItem - - case .search: - let toolbarItem = NSSearchToolbarItem(itemIdentifier: .search) - let description = NSLocalizedString("Search", comment: "Search") - toolbarItem.toolTip = description - toolbarItem.label = description - return toolbarItem - - case .cleanUp: - let title = NSLocalizedString("Clean Up", comment: "Clean Up") - return buildToolbarButton(.cleanUp, title, AppAssets.cleanUpImage, "cleanUp:") - - default: - break - } - - } + case .refresh: + let title = NSLocalizedString("Refresh", comment: "Refresh") + return buildToolbarButton(.refresh, title, AppAssets.refreshImage, "refreshAll:") - return nil + case .newSidebarItemMenu: + let toolbarItem = NSMenuToolbarItem(itemIdentifier: .newSidebarItemMenu) + toolbarItem.image = AppAssets.addNewSidebarItemImage + let description = NSLocalizedString("Add Item", comment: "Add Item") + toolbarItem.toolTip = description + toolbarItem.label = description + toolbarItem.menu = buildNewSidebarItemMenu() + return toolbarItem + + case .markAllAsRead: + let title = NSLocalizedString("Mark All as Read", comment: "Mark All as Read") + return buildToolbarButton(.markAllAsRead, title, AppAssets.markAllAsReadImage, "markAllAsRead:") + + case .toggleReadArticlesFilter: + let title = NSLocalizedString("Read Articles Filter", comment: "Read Articles Filter") + return buildToolbarButton(.toggleReadArticlesFilter, title, AppAssets.filterInactive, "toggleReadArticlesFilter:") + + case .timelineTrackingSeparator: + return NSTrackingSeparatorToolbarItem(identifier: .timelineTrackingSeparator, splitView: splitViewController!.splitView, dividerIndex: 1) + + case .markRead: + let title = NSLocalizedString("Mark Read", comment: "Mark Read") + return buildToolbarButton(.markRead, title, AppAssets.readClosedImage, "toggleRead:") + + case .markStar: + let title = NSLocalizedString("Star", comment: "Star") + return buildToolbarButton(.markStar, title, AppAssets.starOpenImage, "toggleStarred:") + + case .nextUnread: + let title = NSLocalizedString("Next Unread", comment: "Next Unread") + return buildToolbarButton(.nextUnread, title, AppAssets.nextUnreadImage, "nextUnread:") + + case .readerView: + let toolbarItem = RSToolbarItem(itemIdentifier: .readerView) + toolbarItem.autovalidates = true + let description = NSLocalizedString("Reader View", comment: "Reader View") + toolbarItem.toolTip = description + toolbarItem.label = description + let button = ArticleExtractorButton() + button.action = #selector(toggleArticleExtractor(_:)) + toolbarItem.view = button + return toolbarItem + + case .share: + let title = NSLocalizedString("Share", comment: "Share") + return buildToolbarButton(.share, title, AppAssets.shareImage, "toolbarShowShareMenu:") + + case .openInBrowser: + let title = NSLocalizedString("Open in Browser", comment: "Open in Browser") + return buildToolbarButton(.openInBrowser, title, AppAssets.openInBrowserImage, "openArticleInBrowser:") + + case .articleThemeMenu: + articleThemeMenuToolbarItem.image = AppAssets.articleTheme + let description = NSLocalizedString("Article Theme", comment: "Article Theme") + articleThemeMenuToolbarItem.toolTip = description + articleThemeMenuToolbarItem.label = description + return articleThemeMenuToolbarItem + + case .search: + let toolbarItem = NSSearchToolbarItem(itemIdentifier: .search) + let description = NSLocalizedString("Search", comment: "Search") + toolbarItem.toolTip = description + toolbarItem.label = description + return toolbarItem + + case .cleanUp: + let title = NSLocalizedString("Clean Up", comment: "Clean Up") + return buildToolbarButton(.cleanUp, title, AppAssets.cleanUpImage, "cleanUp:") + + default: + return nil + } } func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { - if #available(macOS 11.0, *) { - return [ - .sidebarToggle, - .refresh, - .newSidebarItemMenu, - .sidebarTrackingSeparator, - .markAllAsRead, - .toggleReadArticlesFilter, - .timelineTrackingSeparator, - .flexibleSpace, - .nextUnread, - .markRead, - .markStar, - .readerView, - .openInBrowser, - .share, - .articleThemeMenu, - .search, - .cleanUp - ] - } else { - return [ - .newFeed, - .newFolder, - .refresh, - .flexibleSpace, - .markAllAsRead, - .search, - .flexibleSpace, - .nextUnread, - .markStar, - .markRead, - .readerView, - .openInBrowser, - .share, - .cleanUp - ] - } + return [ + .sidebarToggle, + .refresh, + .newSidebarItemMenu, + .sidebarTrackingSeparator, + .markAllAsRead, + .toggleReadArticlesFilter, + .timelineTrackingSeparator, + .flexibleSpace, + .nextUnread, + .markRead, + .markStar, + .readerView, + .openInBrowser, + .share, + .articleThemeMenu, + .search, + .cleanUp + ] } func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { - if #available(macOS 11.0, *) { - return [ - .flexibleSpace, - .refresh, - .newSidebarItemMenu, - .sidebarTrackingSeparator, - .markAllAsRead, - .toggleReadArticlesFilter, - .timelineTrackingSeparator, - .markRead, - .markStar, - .nextUnread, - .readerView, - .share, - .openInBrowser, - .flexibleSpace, - .search - ] - } else { - return [ - .newFeed, - .newFolder, - .refresh, - .flexibleSpace, - .markAllAsRead, - .search, - .flexibleSpace, - .nextUnread, - .markStar, - .markRead, - .readerView, - .openInBrowser, - .share - ] - } + return [ + .flexibleSpace, + .refresh, + .newSidebarItemMenu, + .sidebarTrackingSeparator, + .markAllAsRead, + .toggleReadArticlesFilter, + .timelineTrackingSeparator, + .markRead, + .markStar, + .nextUnread, + .readerView, + .share, + .openInBrowser, + .flexibleSpace, + .search + ] } func toolbarWillAddItem(_ notification: Notification) { @@ -955,20 +906,11 @@ extension MainWindowController: NSToolbarDelegate { button.sendAction(on: .leftMouseDown) } - if #available(macOS 11.0, *) { - if item.itemIdentifier == .search, let searchItem = item as? NSSearchToolbarItem { - searchItem.searchField.delegate = self - searchItem.searchField.target = self - searchItem.searchField.action = #selector(runSearch(_:)) - currentSearchField = searchItem.searchField - } - } else { - if item.itemIdentifier == .search, let searchField = item.view as? NSSearchField { - searchField.delegate = self - searchField.target = self - searchField.action = #selector(runSearch(_:)) - currentSearchField = searchField - } + if item.itemIdentifier == .search, let searchItem = item as? NSSearchToolbarItem { + searchItem.searchField.delegate = self + searchItem.searchField.target = self + searchItem.searchField.action = #selector(runSearch(_:)) + currentSearchField = searchItem.searchField } } @@ -977,20 +919,11 @@ extension MainWindowController: NSToolbarDelegate { return } - if #available(macOS 11.0, *) { - if item.itemIdentifier == .search, let searchItem = item as? NSSearchToolbarItem { - searchItem.searchField.delegate = nil - searchItem.searchField.target = nil - searchItem.searchField.action = nil - currentSearchField = nil - } - } else { - if item.itemIdentifier == .search, let searchField = item.view as? NSSearchField { - searchField.delegate = nil - searchField.target = nil - searchField.action = nil - currentSearchField = nil - } + if item.itemIdentifier == .search, let searchItem = item as? NSSearchToolbarItem { + searchItem.searchField.delegate = nil + searchItem.searchField.target = nil + searchItem.searchField.action = nil + currentSearchField = nil } } @@ -1122,7 +1055,7 @@ private extension MainWindowController { menuItem.title = commandName } - if #available(macOS 11.0, *), let toolbarItem = item as? NSToolbarItem, let button = toolbarItem.view as? NSButton { + if let toolbarItem = item as? NSToolbarItem, let button = toolbarItem.view as? NSButton { button.image = markingRead ? AppAssets.readClosedImage : AppAssets.readOpenImage } @@ -1135,77 +1068,34 @@ private extension MainWindowController { } - if #available(macOS 11.0, *) { + guard let toolbarItem = item as? NSToolbarItem, let toolbarButton = toolbarItem.view as? ArticleExtractorButton else { + if let menuItem = item as? NSMenuItem { + menuItem.state = isShowingExtractedArticle ? .on : .off + } + return currentLink != nil + } + + if let webfeed = currentTimelineViewController?.selectedArticles.first?.webFeed { + if webfeed.isFeedProvider { + toolbarButton.isEnabled = false + return false + } else { + toolbarButton.isEnabled = true + } + } - guard let toolbarItem = item as? NSToolbarItem, let toolbarButton = toolbarItem.view as? ArticleExtractorButton else { - if let menuItem = item as? NSMenuItem { - menuItem.state = isShowingExtractedArticle ? .on : .off - } - return currentLink != nil - } - - if let webfeed = currentTimelineViewController?.selectedArticles.first?.webFeed { - if webfeed.isFeedProvider { - toolbarButton.isEnabled = false - return false - } else { - toolbarButton.isEnabled = true - } - } + guard let state = articleExtractor?.state else { + toolbarButton.buttonState = .off + return currentLink != nil + } - guard let state = articleExtractor?.state else { - toolbarButton.buttonState = .off - return currentLink != nil - } - - switch state { - case .processing: - toolbarButton.buttonState = .animated - case .failedToParse: - toolbarButton.buttonState = .error - case .ready, .cancelled, .complete: - toolbarButton.buttonState = isShowingExtractedArticle ? .on : .off - } - - } else { - - guard let toolbarItem = item as? NSToolbarItem, let toolbarButton = toolbarItem.view as? LegacyArticleExtractorButton else { - if let menuItem = item as? NSMenuItem { - menuItem.state = isShowingExtractedArticle ? .on : .off - } - return currentLink != nil - } - - if let webfeed = currentTimelineViewController?.selectedArticles.first?.webFeed { - if webfeed.isFeedProvider { - toolbarButton.isEnabled = false - return false - } else { - toolbarButton.isEnabled = true - } - } - - toolbarButton.state = isShowingExtractedArticle ? .on : .off - - guard let state = articleExtractor?.state else { - toolbarButton.isError = false - toolbarButton.isInProgress = false - toolbarButton.state = .off - return currentLink != nil - } - - switch state { - case .processing: - toolbarButton.isError = false - toolbarButton.isInProgress = true - case .failedToParse: - toolbarButton.isError = true - toolbarButton.isInProgress = false - case .ready, .cancelled, .complete: - toolbarButton.isError = false - toolbarButton.isInProgress = false - } - + switch state { + case .processing: + toolbarButton.buttonState = .animated + case .failedToParse: + toolbarButton.buttonState = .error + case .ready, .cancelled, .complete: + toolbarButton.buttonState = isShowingExtractedArticle ? .on : .off } return true @@ -1255,7 +1145,7 @@ private extension MainWindowController { menuItem.title = commandName } - if #available(macOS 11.0, *), let toolbarItem = item as? NSToolbarItem, let button = toolbarItem.view as? NSButton { + if let toolbarItem = item as? NSToolbarItem, let button = toolbarItem.view as? NSButton { button.image = starring ? AppAssets.starOpenImage : AppAssets.starClosedImage } @@ -1281,7 +1171,7 @@ private extension MainWindowController { guard let isReadFiltered = timelineContainerViewController?.isReadFiltered else { (item as? NSMenuItem)?.title = hideCommand - if #available(macOS 11.0, *), let toolbarItem = item as? NSToolbarItem, let button = toolbarItem.view as? NSButton { + if let toolbarItem = item as? NSToolbarItem, let button = toolbarItem.view as? NSButton { toolbarItem.toolTip = hideCommand button.image = AppAssets.filterInactive } @@ -1290,13 +1180,13 @@ private extension MainWindowController { if isReadFiltered { (item as? NSMenuItem)?.title = showCommand - if #available(macOS 11.0, *), let toolbarItem = item as? NSToolbarItem, let button = toolbarItem.view as? NSButton { + if let toolbarItem = item as? NSToolbarItem, let button = toolbarItem.view as? NSButton { toolbarItem.toolTip = showCommand button.image = AppAssets.filterActive } } else { (item as? NSMenuItem)?.title = hideCommand - if #available(macOS 11.0, *), let toolbarItem = item as? NSToolbarItem, let button = toolbarItem.view as? NSButton { + if let toolbarItem = item as? NSToolbarItem, let button = toolbarItem.view as? NSButton { toolbarItem.toolTip = hideCommand button.image = AppAssets.filterInactive } @@ -1331,49 +1221,39 @@ private extension MainWindowController { guard timelineSourceMode != .search else { let localizedLabel = NSLocalizedString("Search: %@", comment: "Search") window?.title = NSString.localizedStringWithFormat(localizedLabel as NSString, searchString ?? "") as String - if #available(macOS 11.0, *) { - window?.subtitle = "" - } + window?.subtitle = "" return } func setSubtitle(_ count: Int) { let localizedLabel = NSLocalizedString("%d unread", comment: "Unread") let formattedLabel = NSString.localizedStringWithFormat(localizedLabel as NSString, count) - if #available(macOS 11.0, *) { - window?.subtitle = formattedLabel as String - } + window?.subtitle = formattedLabel as String } guard let selectedObjects = selectedObjectsInSidebar(), selectedObjects.count > 0 else { window?.title = appDelegate.appName! - if #available(macOS 11.0, *) { - setSubtitle(appDelegate.unreadCount) - } + setSubtitle(appDelegate.unreadCount) return } guard selectedObjects.count == 1 else { window?.title = NSLocalizedString("Multiple", comment: "Multiple") - if #available(macOS 11.0, *) { - let unreadCount = selectedObjects.reduce(0, { result, selectedObject in - if let unreadCountProvider = selectedObject as? UnreadCountProvider { - return result + unreadCountProvider.unreadCount - } else { - return result - } - }) - setSubtitle(unreadCount) - } + let unreadCount = selectedObjects.reduce(0, { result, selectedObject in + if let unreadCountProvider = selectedObject as? UnreadCountProvider { + return result + unreadCountProvider.unreadCount + } else { + return result + } + }) + setSubtitle(unreadCount) return } if let displayNameProvider = currentFeedOrFolder as? DisplayNameProvider { window?.title = displayNameProvider.nameForDisplay - if #available(macOS 11.0, *) { - if let unreadCountProvider = currentFeedOrFolder as? UnreadCountProvider { - setSubtitle(unreadCountProvider.unreadCount) - } + if let unreadCountProvider = currentFeedOrFolder as? UnreadCountProvider { + setSubtitle(unreadCountProvider.unreadCount) } } } diff --git a/Mac/MainWindow/Sidebar/Cell/SidebarCellAppearance.swift b/Mac/MainWindow/Sidebar/Cell/SidebarCellAppearance.swift index 7a082b434..c13996732 100644 --- a/Mac/MainWindow/Sidebar/Cell/SidebarCellAppearance.swift +++ b/Mac/MainWindow/Sidebar/Cell/SidebarCellAppearance.swift @@ -23,11 +23,7 @@ struct SidebarCellAppearance: Equatable { textFieldFontSize = 11 case .large: imageSize = CGSize(width: 22, height: 22) - if #available(macOS 11.0, *) { - textFieldFontSize = 15 - } else { - textFieldFontSize = 13 - } + textFieldFontSize = 15 default: imageSize = CGSize(width: 19, height: 19) textFieldFontSize = 13 diff --git a/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift b/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift index b288ff911..620041bd1 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift @@ -56,11 +56,7 @@ struct TimelineCellAppearance: Equatable { self.showIcon = showIcon - if #available(macOS 11.0, *) { - cellPadding = NSEdgeInsets(top: 8.0, left: 4.0, bottom: 10.0, right: 4.0) - } else { - cellPadding = NSEdgeInsets(top: 8.0, left: 18.0, bottom: 10.0, right: 18.0) - } + cellPadding = NSEdgeInsets(top: 8.0, left: 4.0, bottom: 10.0, right: 4.0) let margin = self.cellPadding.left + self.unreadCircleDimension + self.unreadCircleMarginRight self.boxLeftMargin = margin diff --git a/Mac/MainWindow/Timeline/TimelineTableRowView.swift b/Mac/MainWindow/Timeline/TimelineTableRowView.swift index bf42f4edd..54d87479c 100644 --- a/Mac/MainWindow/Timeline/TimelineTableRowView.swift +++ b/Mac/MainWindow/Timeline/TimelineTableRowView.swift @@ -59,21 +59,12 @@ class TimelineTableRowView : NSTableRowView { separator!.wantsLayer = true separator!.layer?.backgroundColor = AppAssets.timelineSeparatorColor.cgColor addSubview(separator!) - if #available(macOS 11.0, *) { - NSLayoutConstraint.activate([ - separator!.leadingAnchor.constraint(equalTo: cellView.leadingAnchor, constant: 20), - separator!.trailingAnchor.constraint(equalTo: cellView.trailingAnchor, constant: -4), - separator!.heightAnchor.constraint(equalToConstant: 1), - separator!.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0) - ]) - } else { - NSLayoutConstraint.activate([ - separator!.leadingAnchor.constraint(equalTo: cellView.leadingAnchor, constant: 34), - separator!.trailingAnchor.constraint(equalTo: cellView.trailingAnchor, constant: -28), - separator!.heightAnchor.constraint(equalToConstant: 1), - separator!.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0) - ]) - } + NSLayoutConstraint.activate([ + separator!.leadingAnchor.constraint(equalTo: cellView.leadingAnchor, constant: 20), + separator!.trailingAnchor.constraint(equalTo: cellView.trailingAnchor, constant: -4), + separator!.heightAnchor.constraint(equalToConstant: 1), + separator!.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0) + ]) } } diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index 1467649e4..5c73f40bf 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -207,10 +207,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr tableView.doubleAction = #selector(openArticleInBrowser(_:)) tableView.setDraggingSourceOperationMask(.copy, forLocal: false) tableView.keyboardDelegate = keyboardDelegate - - if #available(macOS 11.0, *) { - tableView.style = .inset - } + tableView.style = .inset if !didRegisterForNotifications { NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil) diff --git a/Mac/Preferences/Accounts/AccountsFeedWranglerWindowController.swift b/Mac/Preferences/Accounts/AccountsFeedWranglerWindowController.swift index d7d185c9d..e42301492 100644 --- a/Mac/Preferences/Accounts/AccountsFeedWranglerWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsFeedWranglerWindowController.swift @@ -128,10 +128,8 @@ class AccountsFeedWranglerWindowController: NSWindowController { // MARK: Autofill func enableAutofill() { - if #available(macOS 11, *) { - usernameTextField.contentType = .username - passwordTextField.contentType = .password - } + usernameTextField.contentType = .username + passwordTextField.contentType = .password } } diff --git a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift index 444e152d0..3a4112ca4 100644 --- a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift @@ -133,10 +133,8 @@ class AccountsFeedbinWindowController: NSWindowController { // MARK: Autofill func enableAutofill() { - if #available(macOS 11, *) { - usernameTextField.contentType = .username - passwordTextField.contentType = .password - } + usernameTextField.contentType = .username + passwordTextField.contentType = .password } } diff --git a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift index 00f47778e..9c70fceb5 100644 --- a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift @@ -129,10 +129,8 @@ class AccountsNewsBlurWindowController: NSWindowController { // MARK: Autofill func enableAutofill() { - if #available(macOS 11, *) { - usernameTextField.contentType = .username - passwordTextField.contentType = .password - } + usernameTextField.contentType = .username + passwordTextField.contentType = .password } } diff --git a/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift b/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift index b00d4646d..0c4d3c569 100644 --- a/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift @@ -197,10 +197,8 @@ class AccountsReaderAPIWindowController: NSWindowController { // MARK: Autofill func enableAutofill() { - if #available(macOS 11, *) { - usernameTextField.contentType = .username - passwordTextField.contentType = .password - } + usernameTextField.contentType = .username + passwordTextField.contentType = .password } } diff --git a/Mac/Preferences/Accounts/AddAccountsView.swift b/Mac/Preferences/Accounts/AddAccountsView.swift index 6fc31957d..fb8fd6687 100644 --- a/Mac/Preferences/Accounts/AddAccountsView.swift +++ b/Mac/Preferences/Accounts/AddAccountsView.swift @@ -102,45 +102,25 @@ struct AddAccountsView: View { HStack(spacing: 12) { Spacer() - if #available(OSX 11.0, *) { - Button(action: { - parent?.dismiss(nil) - }, label: { - Text("Cancel") - .frame(width: 76) - }) - .help("Cancel") - .keyboardShortcut(.cancelAction) - - } else { - Button(action: { - parent?.dismiss(nil) - }, label: { - Text("Cancel") - .frame(width: 76) - }) - .accessibility(label: Text("Add Account")) - } - if #available(OSX 11.0, *) { - Button(action: { - addAccountDelegate?.presentSheetForAccount(selectedAccount) - parent?.dismiss(nil) - }, label: { - Text("Continue") - .frame(width: 76) - }) - .help("Add Account") - .keyboardShortcut(.defaultAction) - - } else { - Button(action: { - addAccountDelegate?.presentSheetForAccount(selectedAccount) - parent?.dismiss(nil) - }, label: { - Text("Continue") - .frame(width: 76) - }) - } + + Button(action: { + parent?.dismiss(nil) + }, label: { + Text("Cancel") + .frame(width: 76) + }) + .help("Cancel") + .keyboardShortcut(.cancelAction) + + Button(action: { + addAccountDelegate?.presentSheetForAccount(selectedAccount) + parent?.dismiss(nil) + }, label: { + Text("Continue") + .frame(width: 76) + }) + .help("Add Account") + .keyboardShortcut(.defaultAction) } .padding(.top, 12) .padding(.bottom, 4) diff --git a/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift b/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift index c8c0581fd..09b4ec3b3 100644 --- a/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift +++ b/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift @@ -34,46 +34,25 @@ struct EnableExtensionPointView: View { HStack(spacing: 12) { Spacer() - if #available(OSX 11.0, *) { - Button(action: { - parent?.dismiss(nil) - }, label: { - Text("Cancel") - .frame(width: 80) - }) - .help("Cancel") - .keyboardShortcut(.cancelAction) - - } else { - Button(action: { - parent?.dismiss(nil) - }, label: { - Text("Cancel") - .frame(width: 80) - }) - .accessibility(label: Text("Add Extension")) - } - if #available(OSX 11.0, *) { - Button(action: { - enabler?.enable(typeFromName(extensionPointTypeName)) - parent?.dismiss(nil) - }, label: { - Text("Continue") - .frame(width: 80) - }) - .help("Add Extension") - .keyboardShortcut(.defaultAction) - .disabled(disableContinue()) - } else { - Button(action: { - enabler?.enable(typeFromName(extensionPointTypeName)) - parent?.dismiss(nil) - }, label: { - Text("Continue") - .frame(width: 80) - }) - .disabled(disableContinue()) - } + Button(action: { + parent?.dismiss(nil) + }, label: { + Text("Cancel") + .frame(width: 80) + }) + .help("Cancel") + .keyboardShortcut(.cancelAction) + + Button(action: { + enabler?.enable(typeFromName(extensionPointTypeName)) + parent?.dismiss(nil) + }, label: { + Text("Continue") + .frame(width: 80) + }) + .help("Add Extension") + .keyboardShortcut(.defaultAction) + .disabled(disableContinue()) } .padding(.top, 12) .padding(.bottom, 4)