Implement Feed protocol.
This commit is contained in:
parent
3fb1a3b8cc
commit
5283d2efbe
|
@ -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 = "<group>"; };
|
||||
510BD110232C3801002692E4 /* AccountMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMetadataFile.swift; sourceTree = "<group>"; };
|
||||
510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedMetadataFile.swift; sourceTree = "<group>"; };
|
||||
511B9803237CD4270028BCAA /* ArticleFetcherType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleFetcherType.swift; sourceTree = "<group>"; };
|
||||
511B9803237CD4270028BCAA /* FeedIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedIdentifier.swift; sourceTree = "<group>"; };
|
||||
513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFeedbinSyncTest.swift; sourceTree = "<group>"; };
|
||||
513323092281082F00C30F19 /* subscriptions_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_initial.json; sourceTree = "<group>"; };
|
||||
5133230B2281088A00C30F19 /* subscriptions_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_add.json; sourceTree = "<group>"; };
|
||||
|
@ -233,6 +234,7 @@
|
|||
5170743B232AEDB500A461A3 /* OPMLFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLFile.swift; sourceTree = "<group>"; };
|
||||
518B2EA52351306200400001 /* Account_project_test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Account_project_test.xcconfig; sourceTree = "<group>"; };
|
||||
51BB7B83233531BC008E8144 /* AccountBehaviors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountBehaviors.swift; sourceTree = "<group>"; };
|
||||
51BC8FCB237EC055004F8B56 /* Feed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Feed.swift; sourceTree = "<group>"; };
|
||||
51D58754227F53BE00900287 /* FeedbinTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinTag.swift; sourceTree = "<group>"; };
|
||||
51D58757227F630B00900287 /* tags_delete.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_delete.json; sourceTree = "<group>"; };
|
||||
51D58758227F630B00900287 /* tags_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = tags_add.json; sourceTree = "<group>"; };
|
||||
|
@ -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 */,
|
||||
|
|
|
@ -11,8 +11,6 @@ import Articles
|
|||
|
||||
public protocol ArticleFetcher {
|
||||
|
||||
var articleFetcherType: ArticleFetcherType? { get }
|
||||
|
||||
func fetchArticles() -> Set<Article>
|
||||
func fetchArticlesAsync(_ callback: @escaping ArticleSetBlock)
|
||||
func fetchUnreadArticles() -> Set<Article>
|
||||
|
@ -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<Article> {
|
||||
return account?.fetchArticles(.webFeed(self)) ?? Set<Article>()
|
||||
}
|
||||
|
@ -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<Article> {
|
||||
return fetchUnreadArticles()
|
||||
|
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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<WebFeed> = Set<WebFeed>()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<Article> {
|
||||
return delegate.fetchArticles()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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<Article> {
|
||||
return fetchUnreadArticles()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue