diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index af49c7db9..f5c99af54 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -12,7 +12,7 @@ 5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09C227DE77700C7C3C5 /* TestTransport.swift */; }; 510BD111232C3801002692E4 /* AccountMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD110232C3801002692E4 /* AccountMetadataFile.swift */; }; 510BD113232C3E9D002692E4 /* WebFeedMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */; }; - 511B9804237CD4270028BCAA /* ArticleFetcherType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9803237CD4270028BCAA /* ArticleFetcherType.swift */; }; + 511B9804237CD4270028BCAA /* FeedIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9803237CD4270028BCAA /* FeedIdentifier.swift */; }; 513323082281070D00C30F19 /* AccountFeedbinSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */; }; 5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 513323092281082F00C30F19 /* subscriptions_initial.json */; }; 5133230C2281088A00C30F19 /* subscriptions_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 5133230B2281088A00C30F19 /* subscriptions_add.json */; }; @@ -32,6 +32,7 @@ 5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5165D73022837F3400D9D53D /* InitialFeedDownloader.swift */; }; 5170743C232AEDB500A461A3 /* OPMLFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5170743B232AEDB500A461A3 /* OPMLFile.swift */; }; 51BB7B84233531BC008E8144 /* AccountBehaviors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7B83233531BC008E8144 /* AccountBehaviors.swift */; }; + 51BC8FCC237EC055004F8B56 /* Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC8FCB237EC055004F8B56 /* Feed.swift */; }; 51D58755227F53BE00900287 /* FeedbinTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D58754227F53BE00900287 /* FeedbinTag.swift */; }; 51D5875A227F630B00900287 /* tags_delete.json in Resources */ = {isa = PBXBuildFile; fileRef = 51D58757227F630B00900287 /* tags_delete.json */; }; 51D5875B227F630B00900287 /* tags_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 51D58758227F630B00900287 /* tags_add.json */; }; @@ -212,7 +213,7 @@ 5107A09C227DE77700C7C3C5 /* TestTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestTransport.swift; sourceTree = ""; }; 510BD110232C3801002692E4 /* AccountMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMetadataFile.swift; sourceTree = ""; }; 510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedMetadataFile.swift; sourceTree = ""; }; - 511B9803237CD4270028BCAA /* ArticleFetcherType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleFetcherType.swift; sourceTree = ""; }; + 511B9803237CD4270028BCAA /* FeedIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedIdentifier.swift; sourceTree = ""; }; 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFeedbinSyncTest.swift; sourceTree = ""; }; 513323092281082F00C30F19 /* subscriptions_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_initial.json; sourceTree = ""; }; 5133230B2281088A00C30F19 /* subscriptions_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_add.json; sourceTree = ""; }; @@ -233,6 +234,7 @@ 5170743B232AEDB500A461A3 /* OPMLFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLFile.swift; sourceTree = ""; }; 518B2EA52351306200400001 /* Account_project_test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_project_test.xcconfig; sourceTree = ""; }; 51BB7B83233531BC008E8144 /* AccountBehaviors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountBehaviors.swift; sourceTree = ""; }; + 51BC8FCB237EC055004F8B56 /* Feed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Feed.swift; sourceTree = ""; }; 51D58754227F53BE00900287 /* FeedbinTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinTag.swift; sourceTree = ""; }; 51D58757227F630B00900287 /* tags_delete.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_delete.json; sourceTree = ""; }; 51D58758227F630B00900287 /* tags_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_add.json; sourceTree = ""; }; @@ -531,16 +533,17 @@ 84AF4EA3222CFDD100F6A800 /* AccountMetadata.swift */, 510BD110232C3801002692E4 /* AccountMetadataFile.swift */, 84F73CF0202788D80000BCEF /* ArticleFetcher.swift */, - 511B9803237CD4270028BCAA /* ArticleFetcherType.swift */, 84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */, 8419740D1F6DD25F006346C4 /* Container.swift */, 84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */, 84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */, + 51BC8FCB237EC055004F8B56 /* Feed.swift */, + 511B9803237CD4270028BCAA /* FeedIdentifier.swift */, + 841974001F6DD1EC006346C4 /* Folder.swift */, + 844B297E210CE37E004020B3 /* UnreadCountProvider.swift */, 844B297C2106C7EC004020B3 /* WebFeed.swift */, 84B2D4CE2238C13D00498ADA /* WebFeedMetadata.swift */, 510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */, - 841974001F6DD1EC006346C4 /* Folder.swift */, - 844B297E210CE37E004020B3 /* UnreadCountProvider.swift */, 5165D71F22835E9800D9D53D /* FeedFinder */, 515E4EB12324FF7D0057B0E7 /* Credentials */, 8419742B1F6DDE84006346C4 /* LocalAccount */, @@ -966,7 +969,7 @@ 9E12B0202334696A00ADE5A0 /* FeedlyCreateFeedsForCollectionFoldersOperation.swift in Sources */, 552032FD229D5D5A009559E0 /* ReaderAPITagging.swift in Sources */, 9EAEC62A23331EE70085D7C9 /* FeedlyOrigin.swift in Sources */, - 511B9804237CD4270028BCAA /* ArticleFetcherType.swift in Sources */, + 511B9804237CD4270028BCAA /* FeedIdentifier.swift in Sources */, 84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */, 9E713653233AD63E00765C84 /* FeedlySetUnreadArticlesOperation.swift in Sources */, 841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */, @@ -1002,6 +1005,7 @@ 5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */, 84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */, 9E510D6E234F16A8002E6F1A /* FeedlyAddFeedRequest.swift in Sources */, + 51BC8FCC237EC055004F8B56 /* Feed.swift in Sources */, 846E77501F6EF9C400A165E2 /* LocalAccountRefresher.swift in Sources */, 55203300229D5D5A009559E0 /* ReaderAPICaller.swift in Sources */, 9E1D154F233371DD00F4944C /* FeedlyGetCollectionsOperation.swift in Sources */, diff --git a/Frameworks/Account/ArticleFetcher.swift b/Frameworks/Account/ArticleFetcher.swift index 43509b866..4bf3f647e 100644 --- a/Frameworks/Account/ArticleFetcher.swift +++ b/Frameworks/Account/ArticleFetcher.swift @@ -11,8 +11,6 @@ import Articles public protocol ArticleFetcher { - var articleFetcherType: ArticleFetcherType? { get } - func fetchArticles() -> Set
func fetchArticlesAsync(_ callback: @escaping ArticleSetBlock) func fetchUnreadArticles() -> Set
@@ -21,14 +19,6 @@ public protocol ArticleFetcher { extension WebFeed: ArticleFetcher { - public var articleFetcherType: ArticleFetcherType? { - guard let accountID = account?.accountID else { - assertionFailure("Expected feed.account, but got nil.") - return nil - } - return ArticleFetcherType.webFeed(accountID, webFeedID) - } - public func fetchArticles() -> Set
{ return account?.fetchArticles(.webFeed(self)) ?? Set
() } @@ -57,14 +47,6 @@ extension WebFeed: ArticleFetcher { } extension Folder: ArticleFetcher { - - public var articleFetcherType: ArticleFetcherType? { - guard let accountID = account?.accountID else { - assertionFailure("Expected feed.account, but got nil.") - return nil - } - return ArticleFetcherType.folder(accountID, nameForDisplay) - } public func fetchArticles() -> Set
{ return fetchUnreadArticles() diff --git a/Frameworks/Account/Feed.swift b/Frameworks/Account/Feed.swift new file mode 100644 index 000000000..3fcf1e3d2 --- /dev/null +++ b/Frameworks/Account/Feed.swift @@ -0,0 +1,14 @@ +// +// Feed.swift +// Account +// +// Created by Maurice Parker on 11/15/19. +// Copyright © 2019 Ranchero Software, LLC. All rights reserved. +// + +import Foundation +import RSCore + +public protocol Feed: FeedIdentifiable, ArticleFetcher, DisplayNameProvider, UnreadCountProvider { + +} diff --git a/Frameworks/Account/ArticleFetcherType.swift b/Frameworks/Account/FeedIdentifier.swift similarity index 85% rename from Frameworks/Account/ArticleFetcherType.swift rename to Frameworks/Account/FeedIdentifier.swift index 02912fb5f..6b462b99f 100644 --- a/Frameworks/Account/ArticleFetcherType.swift +++ b/Frameworks/Account/FeedIdentifier.swift @@ -8,7 +8,11 @@ import Foundation -public enum ArticleFetcherType: CustomStringConvertible { +public protocol FeedIdentifiable { + var feedID: FeedIdentifier? { get } +} + +public enum FeedIdentifier: CustomStringConvertible { case smartFeed(String) // String is a unique identifier case script(String) // String is a unique identifier @@ -61,16 +65,16 @@ public enum ArticleFetcherType: CustomStringConvertible { switch type { case "smartFeed": guard let id = userInfo["id"] as? String else { return nil } - self = ArticleFetcherType.smartFeed(id) + self = FeedIdentifier.smartFeed(id) case "script": guard let id = userInfo["id"] as? String else { return nil } - self = ArticleFetcherType.script(id) + self = FeedIdentifier.script(id) case "feed": guard let accountID = userInfo["accountID"] as? String, let webFeedID = userInfo["webFeedID"] as? String else { return nil } - self = ArticleFetcherType.webFeed(accountID, webFeedID) + self = FeedIdentifier.webFeed(accountID, webFeedID) case "folder": guard let accountID = userInfo["accountID"] as? String, let folderName = userInfo["folderName"] as? String else { return nil } - self = ArticleFetcherType.folder(accountID, folderName) + self = FeedIdentifier.folder(accountID, folderName) default: return nil } diff --git a/Frameworks/Account/Folder.swift b/Frameworks/Account/Folder.swift index 47b4292b6..cf6f1df57 100644 --- a/Frameworks/Account/Folder.swift +++ b/Frameworks/Account/Folder.swift @@ -10,7 +10,15 @@ import Foundation import Articles import RSCore -public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCountProvider, Hashable { +public final class Folder: Feed, Renamable, Container, Hashable { + + public var feedID: FeedIdentifier? { + guard let accountID = account?.accountID else { + assertionFailure("Expected feed.account, but got nil.") + return nil + } + return FeedIdentifier.folder(accountID, nameForDisplay) + } public weak var account: Account? public var topLevelWebFeeds: Set = Set() diff --git a/Frameworks/Account/WebFeed.swift b/Frameworks/Account/WebFeed.swift index 1ceb79bdc..929701123 100644 --- a/Frameworks/Account/WebFeed.swift +++ b/Frameworks/Account/WebFeed.swift @@ -11,7 +11,15 @@ import RSCore import RSWeb import Articles -public final class WebFeed: DisplayNameProvider, Renamable, UnreadCountProvider, Hashable { +public final class WebFeed: Feed, Renamable, Hashable { + + public var feedID: FeedIdentifier? { + guard let accountID = account?.accountID else { + assertionFailure("Expected feed.account, but got nil.") + return nil + } + return FeedIdentifier.webFeed(accountID, webFeedID) + } public weak var account: Account? public let url: String diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index 7bf7ea6b5..ff050a501 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -481,7 +481,7 @@ extension MainWindowController: TimelineContainerViewControllerDelegate { let detailState: DetailState if let articles = articles { if articles.count == 1 { - activityManager.reading(fetcher: nil, article: articles.first) + activityManager.reading(feed: nil, article: articles.first) if articles.first?.webFeed?.isArticleExtractorAlwaysOn ?? false { detailState = .loading startArticleExtractorForCurrentLink() diff --git a/Shared/Activity/ActivityManager.swift b/Shared/Activity/ActivityManager.swift index dc1b216a8..25403da8f 100644 --- a/Shared/Activity/ActivityManager.swift +++ b/Shared/Activity/ActivityManager.swift @@ -38,13 +38,13 @@ class ActivityManager { invalidateNextUnread() } - func selecting(fetcher: ArticleFetcher) { + func selecting(feed: Feed) { invalidateCurrentActivities() - selectingActivity = makeSelectFeedActivity(fetcher: fetcher) + selectingActivity = makeSelectFeedActivity(feed: feed) - if let feed = fetcher as? WebFeed { - updateSelectingActivityFeedSearchAttributes(with: feed) + if let webFeed = feed as? WebFeed { + updateSelectingActivityFeedSearchAttributes(with: webFeed) } donate(selectingActivity!) @@ -76,12 +76,12 @@ class ActivityManager { nextUnreadActivity = nil } - func reading(fetcher: ArticleFetcher?, article: Article?) { + func reading(feed: Feed?, article: Article?) { invalidateReading() invalidateNextUnread() guard let article = article else { return } - readingActivity = makeReadArticleActivity(fetcher: fetcher, article: article) + readingActivity = makeReadArticleActivity(feed: feed, article: article) #if os(iOS) updateReadArticleSearchAttributes(with: article) @@ -151,37 +151,36 @@ class ActivityManager { private extension ActivityManager { - func makeSelectFeedActivity(fetcher: ArticleFetcher) -> NSUserActivity { + func makeSelectFeedActivity(feed: Feed) -> NSUserActivity { let activity = NSUserActivity(activityType: ActivityType.selectFeed.rawValue) let localizedText = NSLocalizedString("See articles in “%@”", comment: "See articles in Folder") - let displayName = (fetcher as? DisplayNameProvider)?.nameForDisplay ?? "" - let title = NSString.localizedStringWithFormat(localizedText as NSString, displayName) as String + let title = NSString.localizedStringWithFormat(localizedText as NSString, feed.nameForDisplay) as String activity.title = title activity.keywords = Set(makeKeywords(title)) activity.isEligibleForSearch = true - let articleFetcherIdentifierUserInfo = fetcher.articleFetcherType?.userInfo ?? [AnyHashable: Any]() + let articleFetcherIdentifierUserInfo = feed.feedID?.userInfo ?? [AnyHashable: Any]() activity.userInfo = [UserInfoKey.feedIdentifier: articleFetcherIdentifierUserInfo] activity.requiredUserInfoKeys = Set(activity.userInfo!.keys.map { $0 as! String }) #if os(iOS) activity.suggestedInvocationPhrase = title activity.isEligibleForPrediction = true - activity.persistentIdentifier = fetcher.articleFetcherType?.description ?? "" - activity.contentAttributeSet?.relatedUniqueIdentifier = fetcher.articleFetcherType?.description ?? "" + activity.persistentIdentifier = feed.feedID?.description ?? "" + activity.contentAttributeSet?.relatedUniqueIdentifier = feed.feedID?.description ?? "" #endif return activity } - func makeReadArticleActivity(fetcher: ArticleFetcher?, article: Article) -> NSUserActivity { + func makeReadArticleActivity(feed: Feed?, article: Article) -> NSUserActivity { let activity = NSUserActivity(activityType: ActivityType.readArticle.rawValue) activity.title = ArticleStringFormatter.truncatedTitle(article) - if let fetcher = fetcher { - let articleFetcherIdentifierUserInfo = fetcher.articleFetcherType?.userInfo ?? [AnyHashable: Any]() + if let feed = feed { + let articleFetcherIdentifierUserInfo = feed.feedID?.userInfo ?? [AnyHashable: Any]() let articlePathUserInfo = article.pathUserInfo activity.userInfo = [UserInfoKey.feedIdentifier: articleFetcherIdentifierUserInfo, UserInfoKey.articlePath: articlePathUserInfo] } else { diff --git a/Shared/SmartFeeds/PseudoFeed.swift b/Shared/SmartFeeds/PseudoFeed.swift index 4d2e323ad..8a9c7769d 100644 --- a/Shared/SmartFeeds/PseudoFeed.swift +++ b/Shared/SmartFeeds/PseudoFeed.swift @@ -13,12 +13,11 @@ import Articles import Account import RSCore -protocol PseudoFeed: class, DisplayNameProvider, UnreadCountProvider, SmallIconProvider, PasteboardWriterOwner { +protocol PseudoFeed: class, Feed, SmallIconProvider, PasteboardWriterOwner { } private var smartFeedIcon: RSImage = { - return RSImage(named: NSImage.smartBadgeTemplateName)! }() @@ -35,7 +34,7 @@ import Articles import Account import RSCore -protocol PseudoFeed: class, DisplayNameProvider, UnreadCountProvider, SmallIconProvider { +protocol PseudoFeed: class, Feed, SmallIconProvider { } diff --git a/Shared/SmartFeeds/SearchFeedDelegate.swift b/Shared/SmartFeeds/SearchFeedDelegate.swift index 6b1cdaca4..aacbc704c 100644 --- a/Shared/SmartFeeds/SearchFeedDelegate.swift +++ b/Shared/SmartFeeds/SearchFeedDelegate.swift @@ -13,8 +13,8 @@ import Articles struct SearchFeedDelegate: SmartFeedDelegate { - var articleFetcherType: ArticleFetcherType? { - return ArticleFetcherType.smartFeed(String(describing: SearchFeedDelegate.self)) + var feedID: FeedIdentifier? { + return FeedIdentifier.smartFeed(String(describing: SearchFeedDelegate.self)) } var nameForDisplay: String { diff --git a/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift b/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift index 3df0bc222..3d2af5fb5 100644 --- a/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift +++ b/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift @@ -13,8 +13,8 @@ import Articles struct SearchTimelineFeedDelegate: SmartFeedDelegate { - var articleFetcherType: ArticleFetcherType? { - return ArticleFetcherType.smartFeed(String(describing: SearchTimelineFeedDelegate.self)) + var feedID: FeedIdentifier? { + return FeedIdentifier.smartFeed(String(describing: SearchTimelineFeedDelegate.self)) } var nameForDisplay: String { diff --git a/Shared/SmartFeeds/SmartFeed.swift b/Shared/SmartFeeds/SmartFeed.swift index aef721a58..0419c47a1 100644 --- a/Shared/SmartFeeds/SmartFeed.swift +++ b/Shared/SmartFeeds/SmartFeed.swift @@ -13,6 +13,10 @@ import Account final class SmartFeed: PseudoFeed { + var feedID: FeedIdentifier? { + delegate.feedID + } + var nameForDisplay: String { return delegate.nameForDisplay } @@ -71,10 +75,6 @@ final class SmartFeed: PseudoFeed { } extension SmartFeed: ArticleFetcher { - - var articleFetcherType: ArticleFetcherType? { - delegate.articleFetcherType - } func fetchArticles() -> Set
{ return delegate.fetchArticles() diff --git a/Shared/SmartFeeds/SmartFeedDelegate.swift b/Shared/SmartFeeds/SmartFeedDelegate.swift index a39d4b411..2f831a8e8 100644 --- a/Shared/SmartFeeds/SmartFeedDelegate.swift +++ b/Shared/SmartFeeds/SmartFeedDelegate.swift @@ -11,10 +11,8 @@ import Account import Articles import RSCore -protocol SmartFeedDelegate: DisplayNameProvider, ArticleFetcher, SmallIconProvider { - +protocol SmartFeedDelegate: FeedIdentifiable, DisplayNameProvider, ArticleFetcher, SmallIconProvider { var fetchType: FetchType { get } - func fetchUnreadCount(for: Account, callback: @escaping (Int) -> Void) } diff --git a/Shared/SmartFeeds/StarredFeedDelegate.swift b/Shared/SmartFeeds/StarredFeedDelegate.swift index df4ad368b..81f05fbf9 100644 --- a/Shared/SmartFeeds/StarredFeedDelegate.swift +++ b/Shared/SmartFeeds/StarredFeedDelegate.swift @@ -15,8 +15,8 @@ import Account struct StarredFeedDelegate: SmartFeedDelegate { - var articleFetcherType: ArticleFetcherType? { - return ArticleFetcherType.smartFeed(String(describing: StarredFeedDelegate.self)) + var feedID: FeedIdentifier? { + return FeedIdentifier.smartFeed(String(describing: StarredFeedDelegate.self)) } let nameForDisplay = NSLocalizedString("Starred", comment: "Starred pseudo-feed title") diff --git a/Shared/SmartFeeds/TodayFeedDelegate.swift b/Shared/SmartFeeds/TodayFeedDelegate.swift index 37f67723f..0bd70f653 100644 --- a/Shared/SmartFeeds/TodayFeedDelegate.swift +++ b/Shared/SmartFeeds/TodayFeedDelegate.swift @@ -13,8 +13,8 @@ import Account struct TodayFeedDelegate: SmartFeedDelegate { - var articleFetcherType: ArticleFetcherType? { - return ArticleFetcherType.smartFeed(String(describing: TodayFeedDelegate.self)) + var feedID: FeedIdentifier? { + return FeedIdentifier.smartFeed(String(describing: TodayFeedDelegate.self)) } let nameForDisplay = NSLocalizedString("Today", comment: "Today pseudo-feed title") diff --git a/Shared/SmartFeeds/UnreadFeed.swift b/Shared/SmartFeeds/UnreadFeed.swift index 8955597a2..f41793988 100644 --- a/Shared/SmartFeeds/UnreadFeed.swift +++ b/Shared/SmartFeeds/UnreadFeed.swift @@ -19,6 +19,10 @@ import Articles final class UnreadFeed: PseudoFeed { + var feedID: FeedIdentifier? { + return FeedIdentifier.smartFeed(String(describing: UnreadFeed.self)) + } + let nameForDisplay = NSLocalizedString("All Unread", comment: "All Unread pseudo-feed title") let fetchType = FetchType.unread @@ -53,10 +57,6 @@ final class UnreadFeed: PseudoFeed { extension UnreadFeed: ArticleFetcher { - var articleFetcherType: ArticleFetcherType? { - return ArticleFetcherType.smartFeed(String(describing: UnreadFeed.self)) - } - func fetchArticles() -> Set
{ return fetchUnreadArticles() } diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index b0acd64ba..96010c8b6 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -93,7 +93,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } var node: Node? = nil - if let coordinator = representedObject as? SceneCoordinator, let fetcher = coordinator.timelineFetcher { + if let coordinator = representedObject as? SceneCoordinator, let fetcher = coordinator.timelineFeed { node = coordinator.rootNode.descendantNodeRepresentingObject(fetcher as AnyObject) } else { node = coordinator.rootNode.descendantNodeRepresentingObject(representedObject as AnyObject) diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 1ff1ad068..ec9cce0a0 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -368,7 +368,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner } @objc func displayNameDidChange(_ note: Notification) { - titleView?.label.text = coordinator.timelineName + titleView?.label.text = coordinator.timelineFeed?.nameForDisplay } @objc func scrollPositionDidChange() { @@ -455,16 +455,16 @@ extension MasterTimelineViewController: UISearchBarDelegate { private extension MasterTimelineViewController { func resetUI() { - title = coordinator.timelineName + title = coordinator.timelineFeed?.nameForDisplay if let titleView = Bundle.main.loadNibNamed("MasterTimelineTitleView", owner: self, options: nil)?[0] as? MasterTimelineTitleView { self.titleView = titleView titleView.iconView.iconImage = coordinator.timelineIconImage - titleView.label.text = coordinator.timelineName + titleView.label.text = coordinator.timelineFeed?.nameForDisplay updateTitleUnreadCount() - if coordinator.timelineFetcher is WebFeed { + if coordinator.timelineFeed is WebFeed { titleView.heightAnchor.constraint(equalToConstant: 44.0).isActive = true let tap = UITapGestureRecognizer(target: self, action:#selector(showFeedInspector(_:))) titleView.addGestureRecognizer(tap) @@ -494,7 +494,7 @@ private extension MasterTimelineViewController { } func updateTitleUnreadCount() { - if let unreadCountProvider = coordinator.timelineFetcher as? UnreadCountProvider { + if let unreadCountProvider = coordinator.timelineFeed as? UnreadCountProvider { self.titleView?.unreadCountView.unreadCount = unreadCountProvider.unreadCount } } diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 9ae4e663e..21de1824d 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -110,7 +110,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { private(set) var currentFeedIndexPath: IndexPath? var timelineIconImage: IconImage? { - if let feed = timelineFetcher as? WebFeed { + if let feed = timelineFeed as? WebFeed { let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: feed) if feedIconImage != nil { @@ -123,19 +123,15 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } - return (timelineFetcher as? SmallIconProvider)?.smallIcon + return (timelineFeed as? SmallIconProvider)?.smallIcon } - var timelineName: String? { - return (timelineFetcher as? DisplayNameProvider)?.nameForDisplay - } - - var timelineFetcher: ArticleFetcher? { + var timelineFeed: Feed? { didSet { timelineMiddleIndexPath = nil - if timelineFetcher is WebFeed { + if timelineFeed is WebFeed { showFeedNames = false } else { showFeedNames = true @@ -259,7 +255,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } var isTimelineUnreadAvailable: Bool { - if let unreadProvider = timelineFetcher as? UnreadCountProvider { + if let unreadProvider = timelineFeed as? UnreadCountProvider { return unreadProvider.unreadCount > 0 } return false @@ -519,7 +515,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func masterFeedIndexPathForCurrentTimeline() -> IndexPath? { - guard let node = treeController.rootNode.descendantNodeRepresentingObject(timelineFetcher as AnyObject) else { + guard let node = treeController.rootNode.descendantNodeRepresentingObject(timelineFeed as AnyObject) else { return nil } return indexPathFor(node) @@ -533,12 +529,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { masterFeedViewController.updateFeedSelection(animated: animated) - if let ip = indexPath, let node = nodeFor(ip), let fetcher = node.representedObject as? ArticleFetcher { - timelineFetcher = fetcher - activityManager.selecting(fetcher: fetcher) + if let ip = indexPath, let node = nodeFor(ip), let feed = node.representedObject as? Feed { + timelineFeed = feed + activityManager.selecting(feed: feed) installTimelineControllerIfNecessary(animated: animated) } else { - timelineFetcher = nil + timelineFeed = nil activityManager.invalidateSelecting() if rootSplitViewController.isCollapsed && navControllerForTimeline().viewControllers.last is MasterTimelineViewController { navControllerForTimeline().popViewController(animated: animated) @@ -582,7 +578,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { stopArticleExtractor() currentArticle = article - activityManager.reading(fetcher: timelineFetcher, article: article) + activityManager.reading(feed: timelineFeed, article: article) if article == nil { if rootSplitViewController.isCollapsed { @@ -621,7 +617,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { func beginSearching() { isSearching = true searchArticleIds = Set(articles.map { $0.articleID }) - timelineFetcher = nil + timelineFeed = nil } func endSearching() { @@ -630,10 +626,10 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { lastSearchScope = nil searchArticleIds = nil - if let ip = currentFeedIndexPath, let node = nodeFor(ip), let fetcher = node.representedObject as? ArticleFetcher { - timelineFetcher = fetcher + if let ip = currentFeedIndexPath, let node = nodeFor(ip), let feed = node.representedObject as? Feed { + timelineFeed = feed } else { - timelineFetcher = nil + timelineFeed = nil } selectArticle(nil) @@ -644,7 +640,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { guard isSearching else { return } if searchString.count < 3 { - timelineFetcher = nil + timelineFeed = nil return } @@ -652,9 +648,9 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { switch searchScope { case .global: - timelineFetcher = SmartFeed(delegate: SearchFeedDelegate(searchString: searchString)) + timelineFeed = SmartFeed(delegate: SearchFeedDelegate(searchString: searchString)) case .timeline: - timelineFetcher = SmartFeed(delegate: SearchTimelineFeedDelegate(searchString: searchString, articleIDs: searchArticleIds!)) + timelineFeed = SmartFeed(delegate: SearchTimelineFeedDelegate(searchString: searchString, articleIDs: searchArticleIds!)) } lastSearchString = searchString @@ -806,7 +802,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } func showFeedInspector() { - guard let feed = timelineFetcher as? WebFeed else { + guard let feed = timelineFeed as? WebFeed else { return } showFeedInspector(for: feed) @@ -1362,11 +1358,11 @@ private extension SceneCoordinator { @objc func fetchAndMergeArticles() { - guard let timelineFetcher = timelineFetcher else { + guard let timelineFeed = timelineFeed else { return } - fetchUnsortedArticlesAsync(for: [timelineFetcher]) { [weak self] (unsortedArticles) in + fetchUnsortedArticlesAsync(for: [timelineFeed]) { [weak self] (unsortedArticles) in // Merge articles by articleID. For any unique articleID in current articles, add to unsortedArticles. guard let strongSelf = self else { return @@ -1395,7 +1391,7 @@ private extension SceneCoordinator { // so that the entire display refreshes at once. // It’s a better user experience this way. cancelPendingAsyncFetches() - guard let timelineFetcher = timelineFetcher else { + guard let timelineFetcher = timelineFeed else { emptyTheTimeline() return } @@ -1407,7 +1403,7 @@ private extension SceneCoordinator { // To be called when we need to do an entire fetch, but an async delay is okay. // Example: we have the Today feed selected, and the calendar day just changed. cancelPendingAsyncFetches() - guard let timelineFetcher = timelineFetcher else { + guard let timelineFetcher = timelineFeed else { emptyTheTimeline() return } @@ -1447,14 +1443,14 @@ private extension SceneCoordinator { } func timelineFetcherContainsAnyPseudoFeed() -> Bool { - if timelineFetcher is PseudoFeed { + if timelineFeed is PseudoFeed { return true } return false } func timelineFetcherContainsAnyFolder() -> Bool { - if timelineFetcher is Folder { + if timelineFeed is Folder { return true } return false @@ -1464,13 +1460,13 @@ private extension SceneCoordinator { // Return true if there’s a match or if a folder contains (recursively) one of feeds - if let feed = timelineFetcher as? WebFeed { + if let feed = timelineFeed as? WebFeed { for oneFeed in feeds { if feed.webFeedID == oneFeed.webFeedID || feed.url == oneFeed.url { return true } } - } else if let folder = timelineFetcher as? Folder { + } else if let folder = timelineFeed as? Folder { for oneFeed in feeds { if folder.hasWebFeed(with: oneFeed.webFeedID) || folder.hasWebFeed(withURL: oneFeed.url) { return true @@ -1625,7 +1621,7 @@ private extension SceneCoordinator { func handleSelectFeed(_ userInfo: [AnyHashable : Any]?) { guard let userInfo = userInfo, let feedIdentifierUserInfo = userInfo[UserInfoKey.feedIdentifier] as? [AnyHashable : Any], - let articleFetcherType = ArticleFetcherType(userInfo: feedIdentifierUserInfo) else { + let articleFetcherType = FeedIdentifier(userInfo: feedIdentifierUserInfo) else { return }