diff --git a/Evergreen.xcodeproj/project.pbxproj b/Evergreen.xcodeproj/project.pbxproj index bf6ded265..92f627412 100644 --- a/Evergreen.xcodeproj/project.pbxproj +++ b/Evergreen.xcodeproj/project.pbxproj @@ -13,7 +13,6 @@ 842E45E51ED8C6B7000A8B52 /* MainWindowSplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45E41ED8C6B7000A8B52 /* MainWindowSplitView.swift */; }; 842E45E71ED8C747000A8B52 /* DB5.plist in Resources */ = {isa = PBXBuildFile; fileRef = 842E45E61ED8C747000A8B52 /* DB5.plist */; }; 84513F901FAA63950023A1A9 /* FeedListControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84513F8F1FAA63950023A1A9 /* FeedListControlsView.swift */; }; - 845B14921FC2028A0013CF92 /* PseudoFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845B14911FC2028A0013CF92 /* PseudoFeed.swift */; }; 845F52ED1FB2B9FC00C10BF0 /* FeedPasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845F52EC1FB2B9FC00C10BF0 /* FeedPasteboardWriter.swift */; }; 846E773D1F6EF67A00A165E2 /* Account.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 846E773A1F6EF5D700A165E2 /* Account.framework */; }; 846E773E1F6EF67A00A165E2 /* Account.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 846E773A1F6EF5D700A165E2 /* Account.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -99,7 +98,9 @@ 84F204CE1FAACB660076E152 /* FeedListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204CD1FAACB660076E152 /* FeedListViewController.swift */; }; 84F204DE1FAACB8B0076E152 /* FeedListTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DD1FAACB8B0076E152 /* FeedListTimelineViewController.swift */; }; 84F204E01FAACBB30076E152 /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; }; - 84F2D5251FC22D8100998D64 /* TodayFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5241FC22D8100998D64 /* TodayFeed.swift */; }; + 84F2D5371FC22FCC00998D64 /* PseudoFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5351FC22FCB00998D64 /* PseudoFeed.swift */; }; + 84F2D5381FC22FCC00998D64 /* TodayFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5361FC22FCB00998D64 /* TodayFeed.swift */; }; + 84F2D53A1FC2308B00998D64 /* UnreadFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */; }; 84FB9A2F1EDCD6C4003D53B9 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; }; 84FB9A301EDCD6C4003D53B9 /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ @@ -398,7 +399,6 @@ 842E45E41ED8C6B7000A8B52 /* MainWindowSplitView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainWindowSplitView.swift; sourceTree = ""; }; 842E45E61ED8C747000A8B52 /* DB5.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = DB5.plist; path = Evergreen/Resources/DB5.plist; sourceTree = ""; }; 84513F8F1FAA63950023A1A9 /* FeedListControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListControlsView.swift; sourceTree = ""; }; - 845B14911FC2028A0013CF92 /* PseudoFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PseudoFeed.swift; sourceTree = ""; }; 845B14A51FC2299E0013CF92 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 845F52EC1FB2B9FC00C10BF0 /* FeedPasteboardWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedPasteboardWriter.swift; sourceTree = ""; }; 846E77161F6EF5D000A165E2 /* Database.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Database.xcodeproj; path = Frameworks/Database/Database.xcodeproj; sourceTree = ""; }; @@ -478,7 +478,9 @@ 84F204CD1FAACB660076E152 /* FeedListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListViewController.swift; sourceTree = ""; }; 84F204DD1FAACB8B0076E152 /* FeedListTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListTimelineViewController.swift; sourceTree = ""; }; 84F204DF1FAACBB30076E152 /* ArticleArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleArray.swift; sourceTree = ""; }; - 84F2D5241FC22D8100998D64 /* TodayFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayFeed.swift; sourceTree = ""; }; + 84F2D5351FC22FCB00998D64 /* PseudoFeed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PseudoFeed.swift; sourceTree = ""; }; + 84F2D5361FC22FCB00998D64 /* TodayFeed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TodayFeed.swift; sourceTree = ""; }; + 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadFeed.swift; sourceTree = ""; }; 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Frameworks/Vendor/Sparkle.framework; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ @@ -592,8 +594,6 @@ 849A97561ED9EB0D007D329B /* Data */ = { isa = PBXGroup; children = ( - 845B14911FC2028A0013CF92 /* PseudoFeed.swift */, - 84F2D5241FC22D8100998D64 /* TodayFeed.swift */, 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */, ); name = Data; @@ -744,6 +744,7 @@ 849A978B1ED9EE4D007D329B /* Feed List */, 849A97901ED9EF65007D329B /* Progress Window */, 84DAEE201F86CAE00058304B /* Importers */, + 84F2D5341FC22FCB00998D64 /* PseudoFeeds */, 849A97561ED9EB0D007D329B /* Data */, 849A97961ED9EFAA007D329B /* Extensions */, 849A97991ED9EFB6007D329B /* Resources */, @@ -895,6 +896,17 @@ path = Evergreen/Resources/KeyboardShortcuts; sourceTree = SOURCE_ROOT; }; + 84F2D5341FC22FCB00998D64 /* PseudoFeeds */ = { + isa = PBXGroup; + children = ( + 84F2D5351FC22FCB00998D64 /* PseudoFeed.swift */, + 84F2D5361FC22FCB00998D64 /* TodayFeed.swift */, + 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */, + ); + name = PseudoFeeds; + path = Evergreen/PseudoFeeds; + sourceTree = ""; + }; 84FB9A2C1EDCD6A4003D53B9 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -1269,8 +1281,10 @@ 845F52ED1FB2B9FC00C10BF0 /* FeedPasteboardWriter.swift in Sources */, 849A975E1ED9EB72007D329B /* MainWindowController.swift in Sources */, 842E45E51ED8C6B7000A8B52 /* MainWindowSplitView.swift in Sources */, + 84F2D53A1FC2308B00998D64 /* UnreadFeed.swift in Sources */, 849A97661ED9EB96007D329B /* SidebarViewController.swift in Sources */, 849A97641ED9EB96007D329B /* SidebarOutlineView.swift in Sources */, + 84F2D5371FC22FCC00998D64 /* PseudoFeed.swift in Sources */, 84702AA41FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift in Sources */, 849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */, 849A97651ED9EB96007D329B /* SidebarTreeControllerDelegate.swift in Sources */, @@ -1288,7 +1302,7 @@ 849A97981ED9EFAA007D329B /* Node-Extensions.swift in Sources */, 849A97531ED9EAC0007D329B /* AddFeedController.swift in Sources */, 849A97831ED9EC63007D329B /* StatusBarView.swift in Sources */, - 84F2D5251FC22D8100998D64 /* TodayFeed.swift in Sources */, + 84F2D5381FC22FCC00998D64 /* TodayFeed.swift in Sources */, 849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */, 849A97921ED9EF65007D329B /* IndeterminateProgressWindowController.swift in Sources */, 849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */, @@ -1306,7 +1320,6 @@ 84B99C691FAE36B800ECDEDB /* FeedListFolder.swift in Sources */, 84F204DE1FAACB8B0076E152 /* FeedListTimelineViewController.swift in Sources */, 849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */, - 845B14921FC2028A0013CF92 /* PseudoFeed.swift in Sources */, 849A97851ED9ECCD007D329B /* PreferencesWindowController.swift in Sources */, 849A977A1ED9EC04007D329B /* TimelineTableCellView.swift in Sources */, 849A97761ED9EC04007D329B /* TimelineCellAppearance.swift in Sources */, diff --git a/Evergreen/AppDelegate.swift b/Evergreen/AppDelegate.swift index 8f3134aff..f1afe9f31 100644 --- a/Evergreen/AppDelegate.swift +++ b/Evergreen/AppDelegate.swift @@ -20,7 +20,7 @@ var currentTheme: VSTheme! var appDelegate: AppDelegate! @NSApplicationMain -class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations { +class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, UnreadCountProvider { let windowControllers = NSMutableArray() var preferencesWindowController: NSWindowController? @@ -46,6 +46,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations { didSet { if unreadCount != oldValue { dockBadge.update() + postUnreadCountDidChangeNotification() } } } @@ -96,7 +97,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations { currentTheme = themeLoader.defaultTheme - pseudoFeeds += [TodayFeed()] + pseudoFeeds = [TodayFeed(), UnreadFeed()] createAndShowMainWindow() diff --git a/Evergreen/Data/TodayFeedDelegate.swift b/Evergreen/Data/TodayFeedDelegate.swift deleted file mode 100644 index b7d3d74e9..000000000 --- a/Evergreen/Data/TodayFeedDelegate.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// TodayFeedDelegate.swift -// Evergreen -// -// Created by Brent Simmons on 11/19/17. -// Copyright © 2017 Ranchero Software. All rights reserved. -// - -import Foundation -import Account - -struct TodayFeedDelegate: PseudoFeedDelegate { - - let nameForDisplay = NSLocalizedString("Today", comment: "Today pseudo-feed title") - - func fetchUnreadCount(for account: Account, callback: @escaping (Int) -> Void) { - - account.fetchUnreadCountForToday(callback) - } -} diff --git a/Evergreen/PseudoFeeds/PseudoFeed.swift b/Evergreen/PseudoFeeds/PseudoFeed.swift new file mode 100644 index 000000000..bcedcf4ca --- /dev/null +++ b/Evergreen/PseudoFeeds/PseudoFeed.swift @@ -0,0 +1,18 @@ +// +// PseudoFeed.swift +// Evergreen +// +// Created by Brent Simmons on 11/19/17. +// Copyright © 2017 Ranchero Software. All rights reserved. +// + +import Foundation +import Data +import RSCore + +protocol PseudoFeed: class, DisplayNameProvider, UnreadCountProvider { + +} + + + diff --git a/Evergreen/PseudoFeeds/TodayFeed.swift b/Evergreen/PseudoFeeds/TodayFeed.swift new file mode 100644 index 000000000..89b2ab12f --- /dev/null +++ b/Evergreen/PseudoFeeds/TodayFeed.swift @@ -0,0 +1,91 @@ +// +// TodayFeed.swift +// Evergreen +// +// Created by Brent Simmons on 11/19/17. +// Copyright © 2017 Ranchero Software. All rights reserved. +// + +import Foundation +import Data +import Account + +final class TodayFeed: PseudoFeed { + + let nameForDisplay = NSLocalizedString("Today", comment: "Today pseudo-feed title") + + var unreadCount = 0 { + didSet { + if unreadCount != oldValue { + postUnreadCountDidChangeNotification() + } + } + } + + private var timer: Timer? + private var unreadCounts = [Account: Int]() + + init() { + + NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) + startTimer() // Fetch unread count at startup + } + + @objc func unreadCountDidChange(_ note: Notification) { + + if note.object is Account { + startTimer() + } + } +} + +private extension TodayFeed { + + // MARK: - Unread Counts + + private func fetchUnreadCount(for account: Account) { + + account.fetchUnreadCountForToday { (accountUnreadCount) in + self.unreadCounts[account] = accountUnreadCount + self.updateUnreadCount() + } + } + + private func fetchUnreadCounts() { + + AccountManager.shared.accounts.forEach { self.fetchUnreadCount(for: $0) } + } + + private func updateUnreadCount() { + + unreadCount = AccountManager.shared.accounts.reduce(0) { (result, account) -> Int in + if let oneUnreadCount = unreadCounts[account] { + return result + oneUnreadCount + } + return result + } + } + + // MARK: - Timer + + func stopTimer() { + + if let timer = timer { + timer.rs_invalidateIfValid() + } + timer = nil + } + + private static let fetchCoalescingDelay: TimeInterval = 0.2 + + func startTimer() { + + stopTimer() + + timer = Timer.scheduledTimer(withTimeInterval: TodayFeed.fetchCoalescingDelay, repeats: false, block: { (timer) in + self.fetchUnreadCounts() + self.stopTimer() + }) + } +} + diff --git a/Evergreen/PseudoFeeds/UnreadFeed.swift b/Evergreen/PseudoFeeds/UnreadFeed.swift new file mode 100644 index 000000000..d80b0e07c --- /dev/null +++ b/Evergreen/PseudoFeeds/UnreadFeed.swift @@ -0,0 +1,36 @@ +// +// UnreadFeed.swift +// Evergreen +// +// Created by Brent Simmons on 11/19/17. +// Copyright © 2017 Ranchero Software. All rights reserved. +// + +import Foundation + +// This just shows the global unread count, which appDelegate already has. Easy. + +final class UnreadFeed: PseudoFeed { + + let nameForDisplay = NSLocalizedString("All Unread", comment: "All Unread pseudo-feed title") + + var unreadCount = 0 { + didSet { + if unreadCount != oldValue { + postUnreadCountDidChangeNotification() + } + } + } + + init() { + + self.unreadCount = appDelegate.unreadCount + NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: appDelegate) + } + + @objc func unreadCountDidChange(_ note: Notification) { + + assert(note.object is AppDelegate) + unreadCount = appDelegate.unreadCount + } +}