diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 13bdbd495..7419badfb 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -47,6 +47,8 @@ 5183CCED22711DCE0010922C /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5183CCEC22711DCE0010922C /* Settings.storyboard */; }; 5183CCEF227125970010922C /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCEE227125970010922C /* SettingsViewController.swift */; }; 51934CCB230F599B006127BE /* ThemedNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CC1230F5963006127BE /* ThemedNavigationController.swift */; }; + 51934CCE2310792F006127BE /* ActivityFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityFactory.swift */; }; + 51934CD023108953006127BE /* ActivityID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCF23108953006127BE /* ActivityID.swift */; }; 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; }; 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; }; 51C451A9226377C200C03939 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; }; @@ -701,6 +703,8 @@ 5183CCEC22711DCE0010922C /* Settings.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = ""; }; 5183CCEE227125970010922C /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 51934CC1230F5963006127BE /* ThemedNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemedNavigationController.swift; sourceTree = ""; }; + 51934CCD2310792F006127BE /* ActivityFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityFactory.swift; sourceTree = ""; }; + 51934CCF23108953006127BE /* ActivityID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityID.swift; sourceTree = ""; }; 5194B5ED22B6965300144881 /* SettingsSubscriptionsImportDocumentPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionsImportDocumentPickerView.swift; sourceTree = ""; }; 5194B5F122B69FCC00144881 /* SettingsSubscriptionsExportDocumentPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionsExportDocumentPickerView.swift; sourceTree = ""; }; 519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = ""; }; @@ -1084,6 +1088,15 @@ path = Settings; sourceTree = ""; }; + 51934CCC231078DC006127BE /* Activity */ = { + isa = PBXGroup; + children = ( + 51934CCD2310792F006127BE /* ActivityFactory.swift */, + 51934CCF23108953006127BE /* ActivityID.swift */, + ); + path = Activity; + sourceTree = ""; + }; 5194B5E222B693EC00144881 /* Wrappers */ = { isa = PBXGroup; children = ( @@ -1630,6 +1643,7 @@ 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */, 51C452AD2265102800C03939 /* Timeline */, 84702AB31FA27AE8006B8943 /* Commands */, + 51934CCC231078DC006127BE /* Activity */, 51C452A822650DA100C03939 /* Article Rendering */, 849A97861ED9ECEF007D329B /* Article Styles */, 84DAEE201F86CAE00058304B /* Importers */, @@ -2434,6 +2448,7 @@ 51C452762265091600C03939 /* MasterTimelineViewController.swift in Sources */, 5183CCE9226F68D90010922C /* AccountRefreshTimer.swift in Sources */, 51C452882265093600C03939 /* AddFeedViewController.swift in Sources */, + 51934CCE2310792F006127BE /* ActivityFactory.swift in Sources */, DF999FF722B5AEFA0064B687 /* SafariView.swift in Sources */, 51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */, 84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */, @@ -2445,6 +2460,7 @@ 51E595AD228E1C2100FCC42B /* AddAccountViewController.swift in Sources */, 51C452A322650A1E00C03939 /* HTMLMetadataDownloader.swift in Sources */, 51C4528D2265095F00C03939 /* AddFolderViewController.swift in Sources */, + 51934CD023108953006127BE /* ActivityID.swift in Sources */, 51C452782265091600C03939 /* MasterTimelineCellData.swift in Sources */, 51C45259226508D300C03939 /* AppDefaults.swift in Sources */, 51C45293226509C800C03939 /* StarredFeedDelegate.swift in Sources */, diff --git a/Shared/Activity/ActivityFactory.swift b/Shared/Activity/ActivityFactory.swift new file mode 100644 index 000000000..be33961c1 --- /dev/null +++ b/Shared/Activity/ActivityFactory.swift @@ -0,0 +1,50 @@ +// +// ActivityFactory.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 8/23/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import Foundation +import CoreSpotlight +import CoreServices +import Articles + +class ActivityFactory { + + static func make(_ article: Article) -> NSUserActivity { + let activity = NSUserActivity(activityType: "com.ranchero.NetNewsWire.ReadArticle") + + activity.title = article.title + + let feedNameKeywords = article.feed?.nameForDisplay.components(separatedBy: " ").filter { $0.count > 2 } ?? [] + let articleTitleKeywords = article.title?.components(separatedBy: " ").filter { $0.count > 2 } ?? [] + let keywords = feedNameKeywords + articleTitleKeywords + activity.keywords = Set(keywords) + + activity.userInfo = [ + ActivityID.accountID.rawValue: article.accountID, + ActivityID.feedID.rawValue: article.feedID, + ActivityID.articleID.rawValue: article.articleID + ] + activity.isEligibleForSearch = true + activity.isEligibleForPrediction = false + activity.isEligibleForHandoff = true + + // CoreSpotlight + let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeCompositeContent as String) + attributeSet.title = article.title + attributeSet.contentDescription = article.summary + attributeSet.keywords = keywords + + if let image = article.avatarImage() { + attributeSet.thumbnailData = image.pngData() + } + + activity.contentAttributeSet = attributeSet + + return activity + } + +} diff --git a/Shared/Activity/ActivityID.swift b/Shared/Activity/ActivityID.swift new file mode 100644 index 000000000..38a395a09 --- /dev/null +++ b/Shared/Activity/ActivityID.swift @@ -0,0 +1,15 @@ +// +// ActivityID.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 8/23/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import Foundation + +enum ActivityID: String { + case accountID = "accountID" + case feedID = "feedID" + case articleID = "articleID" +} diff --git a/Shared/Data/ArticleUtilities.swift b/Shared/Data/ArticleUtilities.swift index be7bc3df8..008e00ba1 100644 --- a/Shared/Data/ArticleUtilities.swift +++ b/Shared/Data/ArticleUtilities.swift @@ -7,6 +7,7 @@ // import Foundation +import RSCore import Articles import Account @@ -56,4 +57,30 @@ extension Article { var logicalDatePublished: Date { return datePublished ?? dateModified ?? status.dateArrived } + + func avatarImage() -> RSImage? { + if let authors = authors { + for author in authors { + if let image = appDelegate.authorAvatarDownloader.image(for: author) { + return image + } + } + } + + guard let feed = feed else { + return nil + } + + let feedIconImage = appDelegate.feedIconDownloader.icon(for: feed) + if feedIconImage != nil { + return feedIconImage + } + + if let faviconImage = appDelegate.faviconDownloader.faviconAsAvatar(for: feed) { + return faviconImage + } + + return FaviconGenerator.favicon(feed) + } + } diff --git a/iOS/AppCoordinator.swift b/iOS/AppCoordinator.swift index baf6b6c88..a7f9578cb 100644 --- a/iOS/AppCoordinator.swift +++ b/iOS/AppCoordinator.swift @@ -210,6 +210,8 @@ class AppCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } } } + + private(set) var readActivity: NSUserActivity? = nil override init() { super.init() @@ -508,7 +510,8 @@ class AppCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { func selectArticle(_ indexPath: IndexPath?) { currentArticleIndexPath = indexPath - + updateReadArticleUserActivity() + if indexPath == nil { if !rootSplitViewController.isCollapsed { let systemMessageViewController = UIStoryboard.main.instantiateController(ofType: SystemMessageViewController.self) @@ -1168,4 +1171,16 @@ private extension AppCoordinator { } + // MARK: NSUserActivity + + func updateReadArticleUserActivity() { + readActivity?.invalidate() + readActivity = nil + + guard let article = currentArticle else { return } + + readActivity = ActivityFactory.make(article) + readActivity?.becomeCurrent() + } + } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index acaec796b..c9d0d1362 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -463,39 +463,11 @@ private extension MasterTimelineViewController { } - func avatarFor(_ article: Article) -> UIImage? { - + func avatarFor(_ article: Article) -> RSImage? { if !coordinator.showAvatars { return nil } - - if let authors = article.authors { - for author in authors { - if let image = avatarForAuthor(author) { - return image - } - } - } - - guard let feed = article.feed else { - return nil - } - - let feedIconImage = appDelegate.feedIconDownloader.icon(for: feed) - if feedIconImage != nil { - return feedIconImage - } - - if let feed = article.feed, let faviconImage = appDelegate.faviconDownloader.faviconAsAvatar(for: feed) { - return faviconImage - } - - return FaviconGenerator.favicon(feed) - - } - - func avatarForAuthor(_ author: Author) -> UIImage? { - return appDelegate.authorAvatarDownloader.image(for: author) + return article.avatarImage() } func featuredImageFor(_ article: Article) -> UIImage? { diff --git a/iOS/Resources/Info.plist b/iOS/Resources/Info.plist index 629c451a9..fcfc523d3 100644 --- a/iOS/Resources/Info.plist +++ b/iOS/Resources/Info.plist @@ -47,6 +47,10 @@ 1 LSRequiresIPhoneOS + NSUserActivityTypes + + com.ranchero.NetNewsWire.ReadArticle + NSAppTransportSecurity NSAllowsArbitraryLoads