2019-08-24 21:57:51 +02:00
|
|
|
//
|
2019-08-25 21:43:11 +02:00
|
|
|
// ActivityManager.swift
|
2019-08-24 21:57:51 +02:00
|
|
|
// NetNewsWire-iOS
|
|
|
|
//
|
|
|
|
// Created by Maurice Parker on 8/23/19.
|
|
|
|
// Copyright © 2019 Ranchero Software. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
import CoreSpotlight
|
|
|
|
import CoreServices
|
2019-08-27 21:20:34 +02:00
|
|
|
import Account
|
2019-08-24 21:57:51 +02:00
|
|
|
import Articles
|
2019-08-26 00:04:15 +02:00
|
|
|
import Intents
|
2019-08-24 21:57:51 +02:00
|
|
|
|
2019-08-25 21:43:11 +02:00
|
|
|
class ActivityManager {
|
2019-08-24 21:57:51 +02:00
|
|
|
|
2019-09-03 22:52:59 +02:00
|
|
|
private var nextUnreadActivity: NSUserActivity?
|
|
|
|
private var selectingActivity: NSUserActivity?
|
|
|
|
private var readingActivity: NSUserActivity?
|
2019-08-26 00:04:15 +02:00
|
|
|
|
2019-09-01 02:30:21 +02:00
|
|
|
var stateRestorationActivity: NSUserActivity? {
|
|
|
|
if readingActivity != nil {
|
|
|
|
return readingActivity
|
|
|
|
}
|
|
|
|
return selectingActivity
|
|
|
|
}
|
|
|
|
|
2019-08-28 18:44:54 +02:00
|
|
|
init() {
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
|
|
|
|
}
|
|
|
|
|
2019-09-01 22:31:11 +02:00
|
|
|
func invalidateCurrentActivities() {
|
2019-09-03 22:52:59 +02:00
|
|
|
invalidateReading()
|
|
|
|
invalidateSelecting()
|
|
|
|
invalidateNextUnread()
|
2019-09-01 22:31:11 +02:00
|
|
|
}
|
|
|
|
|
2019-08-26 00:04:15 +02:00
|
|
|
func selectingToday() {
|
2019-09-03 22:52:59 +02:00
|
|
|
invalidateCurrentActivities()
|
|
|
|
|
|
|
|
let title = NSLocalizedString("See articles for “Today”", comment: "Today")
|
2019-08-27 21:20:34 +02:00
|
|
|
selectingActivity = makeSelectingActivity(type: ActivityType.selectToday, title: title, identifier: "smartfeed.today")
|
2019-08-26 00:04:15 +02:00
|
|
|
selectingActivity!.becomeCurrent()
|
|
|
|
}
|
|
|
|
|
|
|
|
func selectingAllUnread() {
|
2019-09-03 22:52:59 +02:00
|
|
|
invalidateCurrentActivities()
|
|
|
|
|
|
|
|
let title = NSLocalizedString("See articles in “All Unread”", comment: "All Unread")
|
2019-08-27 21:20:34 +02:00
|
|
|
selectingActivity = makeSelectingActivity(type: ActivityType.selectAllUnread, title: title, identifier: "smartfeed.allUnread")
|
2019-08-26 00:04:15 +02:00
|
|
|
selectingActivity!.becomeCurrent()
|
|
|
|
}
|
|
|
|
|
|
|
|
func selectingStarred() {
|
2019-09-03 22:52:59 +02:00
|
|
|
invalidateCurrentActivities()
|
|
|
|
|
|
|
|
let title = NSLocalizedString("See articles in “Starred”", comment: "Starred")
|
2019-08-27 21:20:34 +02:00
|
|
|
selectingActivity = makeSelectingActivity(type: ActivityType.selectStarred, title: title, identifier: "smartfeed.starred")
|
|
|
|
selectingActivity!.becomeCurrent()
|
|
|
|
}
|
|
|
|
|
|
|
|
func selectingFolder(_ folder: Folder) {
|
2019-09-03 22:52:59 +02:00
|
|
|
invalidateCurrentActivities()
|
|
|
|
|
2019-08-27 21:20:34 +02:00
|
|
|
let localizedText = NSLocalizedString("See articles in “%@”", comment: "See articles in Folder")
|
|
|
|
let title = NSString.localizedStringWithFormat(localizedText as NSString, folder.nameForDisplay) as String
|
2019-09-01 02:30:21 +02:00
|
|
|
selectingActivity = makeSelectingActivity(type: ActivityType.selectFolder, title: title, identifier: ActivityManager.identifer(for: folder))
|
2019-08-28 00:43:15 +02:00
|
|
|
|
|
|
|
selectingActivity!.userInfo = [
|
|
|
|
ActivityID.accountID.rawValue: folder.account?.accountID ?? "",
|
|
|
|
ActivityID.accountName.rawValue: folder.account?.name ?? "",
|
|
|
|
ActivityID.folderName.rawValue: folder.nameForDisplay
|
|
|
|
]
|
2019-08-27 21:20:34 +02:00
|
|
|
|
2019-08-28 00:43:15 +02:00
|
|
|
selectingActivity!.becomeCurrent()
|
2019-08-27 21:20:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func selectingFeed(_ feed: Feed) {
|
2019-09-03 22:52:59 +02:00
|
|
|
invalidateCurrentActivities()
|
|
|
|
|
2019-08-27 21:20:34 +02:00
|
|
|
let localizedText = NSLocalizedString("See articles in “%@”", comment: "See articles in Feed")
|
|
|
|
let title = NSString.localizedStringWithFormat(localizedText as NSString, feed.nameForDisplay) as String
|
2019-09-01 02:30:21 +02:00
|
|
|
selectingActivity = makeSelectingActivity(type: ActivityType.selectFeed, title: title, identifier: ActivityManager.identifer(for: feed))
|
2019-08-28 00:43:15 +02:00
|
|
|
|
|
|
|
selectingActivity!.userInfo = [
|
|
|
|
ActivityID.accountID.rawValue: feed.account?.accountID ?? "",
|
|
|
|
ActivityID.accountName.rawValue: feed.account?.name ?? "",
|
|
|
|
ActivityID.feedID.rawValue: feed.feedID
|
|
|
|
]
|
2019-08-28 18:44:54 +02:00
|
|
|
updateSelectingActivityFeedSearchAttributes(with: feed)
|
2019-08-28 18:30:40 +02:00
|
|
|
|
2019-08-26 00:04:15 +02:00
|
|
|
selectingActivity!.becomeCurrent()
|
|
|
|
}
|
|
|
|
|
2019-09-03 22:52:59 +02:00
|
|
|
func invalidateSelecting() {
|
|
|
|
selectingActivity?.invalidate()
|
|
|
|
selectingActivity = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func selectingNextUnread() {
|
|
|
|
guard nextUnreadActivity == nil else { return }
|
|
|
|
let title = NSLocalizedString("See first unread article", comment: "First Unread")
|
|
|
|
nextUnreadActivity = makeSelectingActivity(type: ActivityType.nextUnread, title: title, identifier: "action.nextUnread")
|
|
|
|
nextUnreadActivity!.becomeCurrent()
|
|
|
|
}
|
|
|
|
|
|
|
|
func invalidateNextUnread() {
|
|
|
|
nextUnreadActivity?.invalidate()
|
|
|
|
nextUnreadActivity = nil
|
|
|
|
}
|
|
|
|
|
2019-08-26 00:04:15 +02:00
|
|
|
func reading(_ article: Article?) {
|
2019-09-03 22:52:59 +02:00
|
|
|
invalidateReading()
|
|
|
|
invalidateNextUnread()
|
2019-08-26 00:04:15 +02:00
|
|
|
guard let article = article else { return }
|
|
|
|
readingActivity = makeReadArticleActivity(article)
|
|
|
|
readingActivity?.becomeCurrent()
|
|
|
|
}
|
|
|
|
|
2019-09-03 22:52:59 +02:00
|
|
|
func invalidateReading() {
|
|
|
|
nextUnreadActivity?.invalidate()
|
|
|
|
nextUnreadActivity = nil
|
|
|
|
}
|
|
|
|
|
2019-09-01 02:30:21 +02:00
|
|
|
static func cleanUp(_ account: Account) {
|
2019-08-28 18:30:40 +02:00
|
|
|
var ids = [String]()
|
|
|
|
|
|
|
|
if let folders = account.folders {
|
|
|
|
for folder in folders {
|
|
|
|
ids.append(identifer(for: folder))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for feed in account.flattenedFeeds() {
|
|
|
|
ids.append(contentsOf: identifers(for: feed))
|
|
|
|
}
|
|
|
|
|
|
|
|
NSUserActivity.deleteSavedUserActivities(withPersistentIdentifiers: ids) {}
|
|
|
|
}
|
|
|
|
|
2019-09-01 02:30:21 +02:00
|
|
|
static func cleanUp(_ folder: Folder) {
|
2019-08-28 18:30:40 +02:00
|
|
|
var ids = [String]()
|
|
|
|
ids.append(identifer(for: folder))
|
|
|
|
|
|
|
|
for feed in folder.flattenedFeeds() {
|
|
|
|
ids.append(contentsOf: identifers(for: feed))
|
|
|
|
}
|
|
|
|
|
|
|
|
NSUserActivity.deleteSavedUserActivities(withPersistentIdentifiers: ids) {}
|
|
|
|
}
|
|
|
|
|
2019-09-01 02:30:21 +02:00
|
|
|
static func cleanUp(_ feed: Feed) {
|
2019-08-28 18:30:40 +02:00
|
|
|
NSUserActivity.deleteSavedUserActivities(withPersistentIdentifiers: identifers(for: feed)) {}
|
|
|
|
}
|
|
|
|
|
2019-08-28 18:44:54 +02:00
|
|
|
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
|
|
|
|
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed, let activityFeedId = selectingActivity?.userInfo?[ActivityID.feedID.rawValue] as? String else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if activityFeedId == feed.feedID {
|
|
|
|
updateSelectingActivityFeedSearchAttributes(with: feed)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-26 00:04:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: Private
|
|
|
|
|
|
|
|
private extension ActivityManager {
|
|
|
|
|
2019-08-27 21:20:34 +02:00
|
|
|
func makeSelectingActivity(type: ActivityType, title: String, identifier: String) -> NSUserActivity {
|
2019-08-26 00:04:15 +02:00
|
|
|
let activity = NSUserActivity(activityType: type.rawValue)
|
|
|
|
activity.title = title
|
|
|
|
activity.suggestedInvocationPhrase = title
|
|
|
|
activity.keywords = Set(makeKeywords(title))
|
|
|
|
activity.isEligibleForPrediction = true
|
|
|
|
activity.isEligibleForSearch = true
|
|
|
|
activity.persistentIdentifier = identifier
|
|
|
|
return activity
|
|
|
|
}
|
|
|
|
|
2019-08-25 21:43:11 +02:00
|
|
|
func makeReadArticleActivity(_ article: Article) -> NSUserActivity {
|
2019-08-25 02:31:29 +02:00
|
|
|
let activity = NSUserActivity(activityType: ActivityType.readArticle.rawValue)
|
2019-08-24 21:57:51 +02:00
|
|
|
|
|
|
|
activity.title = article.title
|
|
|
|
|
2019-08-26 00:04:15 +02:00
|
|
|
let feedNameKeywords = makeKeywords(article.feed?.nameForDisplay)
|
|
|
|
let articleTitleKeywords = makeKeywords(article.title)
|
2019-08-24 21:57:51 +02:00
|
|
|
let keywords = feedNameKeywords + articleTitleKeywords
|
|
|
|
activity.keywords = Set(keywords)
|
|
|
|
|
|
|
|
activity.userInfo = [
|
|
|
|
ActivityID.accountID.rawValue: article.accountID,
|
2019-08-25 02:31:29 +02:00
|
|
|
ActivityID.accountName.rawValue: article.account?.name ?? "",
|
2019-08-24 21:57:51 +02:00
|
|
|
ActivityID.feedID.rawValue: article.feedID,
|
|
|
|
ActivityID.articleID.rawValue: article.articleID
|
|
|
|
]
|
|
|
|
activity.isEligibleForSearch = true
|
|
|
|
activity.isEligibleForPrediction = false
|
|
|
|
activity.isEligibleForHandoff = true
|
2019-09-01 02:30:21 +02:00
|
|
|
activity.persistentIdentifier = ActivityManager.identifer(for: article)
|
2019-08-24 21:57:51 +02:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2019-08-26 00:04:15 +02:00
|
|
|
func makeKeywords(_ value: String?) -> [String] {
|
|
|
|
return value?.components(separatedBy: " ").filter { $0.count > 2 } ?? []
|
|
|
|
}
|
|
|
|
|
2019-09-01 02:30:21 +02:00
|
|
|
func updateSelectingActivityFeedSearchAttributes(with feed: Feed) {
|
|
|
|
|
|
|
|
let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String)
|
|
|
|
attributeSet.title = feed.nameForDisplay
|
|
|
|
attributeSet.keywords = makeKeywords(feed.nameForDisplay)
|
|
|
|
if let image = appDelegate.feedIconDownloader.icon(for: feed) {
|
|
|
|
attributeSet.thumbnailData = image.pngData()
|
|
|
|
} else if let image = appDelegate.faviconDownloader.faviconAsAvatar(for: feed) {
|
|
|
|
attributeSet.thumbnailData = image.pngData()
|
|
|
|
}
|
|
|
|
|
|
|
|
selectingActivity!.contentAttributeSet = attributeSet
|
|
|
|
selectingActivity!.needsSave = true
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static func identifer(for folder: Folder) -> String {
|
2019-08-28 18:30:40 +02:00
|
|
|
return "account_\(folder.account!.accountID)_folder_\(folder.nameForDisplay)"
|
|
|
|
}
|
|
|
|
|
2019-09-01 02:30:21 +02:00
|
|
|
static func identifer(for feed: Feed) -> String {
|
2019-08-28 18:30:40 +02:00
|
|
|
return "account_\(feed.account!.accountID)_feed_\(feed.feedID)"
|
|
|
|
}
|
|
|
|
|
2019-09-01 02:30:21 +02:00
|
|
|
static func identifer(for article: Article) -> String {
|
2019-08-28 18:30:40 +02:00
|
|
|
return "account_\(article.accountID)_feed_\(article.feedID)_article_\(article.articleID)"
|
|
|
|
}
|
|
|
|
|
2019-09-01 02:30:21 +02:00
|
|
|
static func identifers(for feed: Feed) -> [String] {
|
2019-08-28 18:30:40 +02:00
|
|
|
var ids = [String]()
|
|
|
|
ids.append(identifer(for: feed))
|
|
|
|
|
|
|
|
for article in feed.fetchArticles() {
|
|
|
|
ids.append(identifer(for: article))
|
|
|
|
}
|
|
|
|
|
|
|
|
return ids
|
|
|
|
}
|
|
|
|
|
2019-08-24 21:57:51 +02:00
|
|
|
}
|