Add the ability to handoff from iOS to Mac
This commit is contained in:
parent
d41da79c72
commit
0000e03083
|
@ -183,7 +183,7 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, De
|
|||
public var deepLinkUserInfo: [AnyHashable : Any] {
|
||||
return [
|
||||
DeepLinkKey.accountID.rawValue: account?.accountID ?? "",
|
||||
DeepLinkKey.accountName.rawValue: account?.name ?? "",
|
||||
DeepLinkKey.accountName.rawValue: account?.nameForDisplay ?? "",
|
||||
DeepLinkKey.feedID.rawValue: feedID
|
||||
]
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCoun
|
|||
public var deepLinkUserInfo: [AnyHashable : Any] {
|
||||
return [
|
||||
DeepLinkKey.accountID.rawValue: account?.accountID ?? "",
|
||||
DeepLinkKey.accountName.rawValue: account?.name ?? "",
|
||||
DeepLinkKey.accountName.rawValue: account?.nameForDisplay ?? "",
|
||||
DeepLinkKey.folderName.rawValue: nameForDisplay
|
||||
]
|
||||
}
|
||||
|
|
|
@ -212,6 +212,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
|||
#endif
|
||||
}
|
||||
|
||||
func application(_ application: NSApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([NSUserActivityRestoring]) -> Void) -> Bool {
|
||||
guard let mainWindowController = mainWindowController else {
|
||||
return false
|
||||
}
|
||||
mainWindowController.handle(userActivity)
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
|
||||
// https://github.com/brentsimmons/NetNewsWire/issues/522
|
||||
// I couldn’t reproduce the crashing bug, but it appears to happen on creating a main window
|
||||
|
|
|
@ -18,6 +18,8 @@ enum TimelineSourceMode {
|
|||
|
||||
class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
||||
|
||||
private var activityManager = ActivityManager()
|
||||
|
||||
private var isShowingExtractedArticle = false
|
||||
private var articleExtractor: ArticleExtractor? = nil
|
||||
private var sharingServicePickerDelegate: NSSharingServicePickerDelegate?
|
||||
|
@ -119,6 +121,12 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
|||
currentTimelineViewController?.goToDeepLink(for: userInfo)
|
||||
}
|
||||
|
||||
func handle(_ activity: NSUserActivity) {
|
||||
guard let userInfo = activity.userInfo else { return }
|
||||
sidebarViewController?.deepLinkRevealAndSelect(for: userInfo)
|
||||
currentTimelineViewController?.goToDeepLink(for: userInfo)
|
||||
}
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
// func window(_ window: NSWindow, willEncodeRestorableState state: NSCoder) {
|
||||
|
@ -456,6 +464,8 @@ extension MainWindowController: SidebarDelegate {
|
|||
extension MainWindowController: TimelineContainerViewControllerDelegate {
|
||||
|
||||
func timelineSelectionDidChange(_: TimelineContainerViewController, articles: [Article]?, mode: TimelineSourceMode) {
|
||||
activityManager.invalidateReading()
|
||||
|
||||
articleExtractor?.cancel()
|
||||
articleExtractor = nil
|
||||
isShowingExtractedArticle = false
|
||||
|
@ -464,6 +474,7 @@ extension MainWindowController: TimelineContainerViewControllerDelegate {
|
|||
let detailState: DetailState
|
||||
if let articles = articles {
|
||||
if articles.count == 1 {
|
||||
activityManager.reading(articles.first!)
|
||||
if articles.first?.feed?.isArticleExtractorAlwaysOn ?? false {
|
||||
detailState = .loading
|
||||
startArticleExtractorForCurrentLink()
|
||||
|
|
|
@ -488,7 +488,7 @@ private extension SidebarViewController {
|
|||
return nil
|
||||
}
|
||||
|
||||
if let node = treeController.rootNode.descendantNode(where: { ($0.representedObject as? Account)?.name == accountName }) {
|
||||
if let node = treeController.rootNode.descendantNode(where: { ($0.representedObject as? Account)?.nameForDisplay == accountName }) {
|
||||
return node
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,10 @@
|
|||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>com.ranchero.NetNewsWire.ReadArticle</string>
|
||||
</array>
|
||||
<key>NSAppleEventsUsageDescription</key>
|
||||
<string>NetNewsWire communicates with other apps on your Mac when you choose to share an article.</string>
|
||||
<key>NSAppleScriptEnabled</key>
|
||||
|
|
|
@ -219,6 +219,8 @@
|
|||
51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FD413A2342BD0500880194 /* MasterTimelineUnreadCountView.swift */; };
|
||||
51FE10032345529D0056195D /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; };
|
||||
51FE10042345529D0056195D /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; };
|
||||
51FE10092346739D0056195D /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; };
|
||||
51FE100A234673A00056195D /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityManager.swift */; };
|
||||
55E15BCB229D65A900D6602A /* AccountsReaderAPI.xib in Resources */ = {isa = PBXBuildFile; fileRef = 55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */; };
|
||||
55E15BCC229D65A900D6602A /* AccountsReaderAPIWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */; };
|
||||
5F323809231DF9F000706F6B /* NNWTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F323808231DF9F000706F6B /* NNWTableViewCell.swift */; };
|
||||
|
@ -2947,6 +2949,7 @@
|
|||
51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */,
|
||||
849A97651ED9EB96007D329B /* FeedTreeControllerDelegate.swift in Sources */,
|
||||
849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */,
|
||||
51FE10092346739D0056195D /* ActivityType.swift in Sources */,
|
||||
840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */,
|
||||
8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */,
|
||||
84C9FC7B22629E1200D921D6 /* AccountsControlsBackgroundView.swift in Sources */,
|
||||
|
@ -2973,6 +2976,7 @@
|
|||
84E185C3203BB12600F69BFA /* MultilineTextFieldSizer.swift in Sources */,
|
||||
8477ACBE22238E9500DF7F37 /* SearchFeedDelegate.swift in Sources */,
|
||||
51E3EB33229AB02C00645299 /* ErrorHandler.swift in Sources */,
|
||||
51FE100A234673A00056195D /* ActivityManager.swift in Sources */,
|
||||
8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */,
|
||||
55E15BCC229D65A900D6602A /* AccountsReaderAPIWindowController.swift in Sources */,
|
||||
5144EA382279FC6200D19003 /* AccountsAddLocalWindowController.swift in Sources */,
|
||||
|
|
|
@ -113,6 +113,7 @@ class ActivityManager {
|
|||
readingActivity = nil
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
static func cleanUp(_ account: Account) {
|
||||
var ids = [String]()
|
||||
|
||||
|
@ -143,6 +144,7 @@ class ActivityManager {
|
|||
static func cleanUp(_ feed: Feed) {
|
||||
NSUserActivity.deleteSavedUserActivities(withPersistentIdentifiers: identifers(for: feed)) {}
|
||||
}
|
||||
#endif
|
||||
|
||||
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
|
||||
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed, let activityFeedId = selectingActivity?.userInfo?[DeepLinkKey.feedID.rawValue] as? String else {
|
||||
|
@ -162,28 +164,33 @@ private extension ActivityManager {
|
|||
func makeSelectingActivity(type: ActivityType, title: String, identifier: String) -> NSUserActivity {
|
||||
let activity = NSUserActivity(activityType: type.rawValue)
|
||||
activity.title = title
|
||||
activity.suggestedInvocationPhrase = title
|
||||
activity.keywords = Set(makeKeywords(title))
|
||||
activity.isEligibleForPrediction = true
|
||||
activity.isEligibleForSearch = true
|
||||
|
||||
#if os(iOS)
|
||||
activity.suggestedInvocationPhrase = title
|
||||
activity.isEligibleForPrediction = true
|
||||
activity.persistentIdentifier = identifier
|
||||
#endif
|
||||
|
||||
return activity
|
||||
}
|
||||
|
||||
func makeReadArticleActivity(_ article: Article) -> NSUserActivity {
|
||||
let activity = NSUserActivity(activityType: ActivityType.readArticle.rawValue)
|
||||
|
||||
activity.title = article.title
|
||||
activity.userInfo = article.deepLinkUserInfo
|
||||
activity.isEligibleForHandoff = true
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
let feedNameKeywords = makeKeywords(article.feed?.nameForDisplay)
|
||||
let articleTitleKeywords = makeKeywords(article.title)
|
||||
let keywords = feedNameKeywords + articleTitleKeywords
|
||||
activity.keywords = Set(keywords)
|
||||
|
||||
activity.userInfo = article.deepLinkUserInfo
|
||||
activity.isEligibleForSearch = true
|
||||
activity.isEligibleForPrediction = false
|
||||
activity.isEligibleForHandoff = true
|
||||
activity.persistentIdentifier = ActivityManager.identifer(for: article)
|
||||
|
||||
// CoreSpotlight
|
||||
|
@ -197,6 +204,7 @@ private extension ActivityManager {
|
|||
}
|
||||
|
||||
activity.contentAttributeSet = attributeSet
|
||||
#endif
|
||||
|
||||
return activity
|
||||
}
|
||||
|
@ -211,9 +219,17 @@ private extension ActivityManager {
|
|||
attributeSet.title = feed.nameForDisplay
|
||||
attributeSet.keywords = makeKeywords(feed.nameForDisplay)
|
||||
if let image = appDelegate.feedIconDownloader.icon(for: feed) {
|
||||
#if os(iOS)
|
||||
attributeSet.thumbnailData = image.pngData()
|
||||
#else
|
||||
attributeSet.thumbnailData = image.tiffRepresentation
|
||||
#endif
|
||||
} else if let image = appDelegate.faviconDownloader.faviconAsAvatar(for: feed) {
|
||||
#if os(iOS)
|
||||
attributeSet.thumbnailData = image.pngData()
|
||||
#else
|
||||
attributeSet.thumbnailData = image.tiffRepresentation
|
||||
#endif
|
||||
}
|
||||
|
||||
selectingActivity!.contentAttributeSet = attributeSet
|
||||
|
|
|
@ -98,7 +98,7 @@ extension Article: DeepLinkProvider {
|
|||
public var deepLinkUserInfo: [AnyHashable : Any] {
|
||||
return [
|
||||
DeepLinkKey.accountID.rawValue: accountID,
|
||||
DeepLinkKey.accountName.rawValue: account?.name ?? "",
|
||||
DeepLinkKey.accountName.rawValue: account?.nameForDisplay ?? "",
|
||||
DeepLinkKey.feedID.rawValue: feedID,
|
||||
DeepLinkKey.articleID.rawValue: articleID
|
||||
]
|
||||
|
|
|
@ -1658,7 +1658,7 @@ private extension SceneCoordinator {
|
|||
return nil
|
||||
}
|
||||
|
||||
if let node = treeController.rootNode.descendantNode(where: { ($0.representedObject as? Account)?.name == accountName }) {
|
||||
if let node = treeController.rootNode.descendantNode(where: { ($0.representedObject as? Account)?.nameForDisplay == accountName }) {
|
||||
return node
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue