Update the timeline cell when an article’s status changes.
This commit is contained in:
parent
e66e6083c7
commit
6572631866
@ -12,13 +12,13 @@ import Account
|
||||
|
||||
// These handle multiple accounts.
|
||||
|
||||
func markArticles(_ articles: Set<Article>, statusKey: String, flag: Bool) {
|
||||
func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) {
|
||||
|
||||
let d: [String: Set<Article>] = accountAndArticlesDictionary(articles)
|
||||
|
||||
d.keys.forEach { (accountID) in
|
||||
for (accountID, accountArticles) in d {
|
||||
|
||||
guard let accountArticles = d[accountID], let account = AccountManager.shared.existingAccount(with: accountID) else {
|
||||
guard let account = AccountManager.shared.existingAccount(with: accountID) else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -28,17 +28,8 @@ func markArticles(_ articles: Set<Article>, statusKey: String, flag: Bool) {
|
||||
|
||||
private func accountAndArticlesDictionary(_ articles: Set<Article>) -> [String: Set<Article>] {
|
||||
|
||||
var d = [String: Set<Article>]()
|
||||
|
||||
articles.forEach { (article) in
|
||||
|
||||
let accountID = article.accountID
|
||||
var articleSet: Set<Article> = d[accountID] ?? Set<Article>()
|
||||
articleSet.insert(article)
|
||||
d[accountID] = articleSet
|
||||
}
|
||||
|
||||
return d
|
||||
let d = Dictionary(grouping: articles, by: { $0.accountID })
|
||||
return d.mapValues{ Set($0) }
|
||||
}
|
||||
|
||||
extension Article {
|
||||
|
@ -76,7 +76,7 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
||||
if !didRegisterForNotifications {
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(sidebarSelectionDidChange(_:)), name: .SidebarSelectionDidChange, object: nil)
|
||||
// NotificationCenter.default.addObserver(self, selector: #selector(articleStatusesDidChange(_:)), name: .ArticleStatusesDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil)
|
||||
|
||||
NSUserDefaultsController.shared.addObserver(self, forKeyPath: timelineFontSizeKVOKey, options: NSKeyValueObservingOptions(rawValue: 0), context: nil)
|
||||
|
||||
@ -115,7 +115,7 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: API
|
||||
// MARK: - API
|
||||
|
||||
func markAllAsRead() {
|
||||
|
||||
@ -123,12 +123,12 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
||||
return
|
||||
}
|
||||
|
||||
markArticles(Set(articles), statusKey: ArticleStatusKey.read.rawValue, flag: true)
|
||||
markArticles(Set(articles), statusKey: .read, flag: true)
|
||||
|
||||
reloadCellsForArticles(articles)
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
// MARK: - Actions
|
||||
|
||||
@objc func openArticleInBrowser(_ sender: AnyObject) {
|
||||
|
||||
@ -146,20 +146,20 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
||||
let status = articles.first!.status
|
||||
let markAsRead = !status.read
|
||||
|
||||
markArticles(Set(articles), statusKey: ArticleStatusKey.read.rawValue, flag: markAsRead)
|
||||
markArticles(Set(articles), statusKey: .read, flag: markAsRead)
|
||||
}
|
||||
|
||||
@IBAction func markSelectedArticlesAsRead(_ sender: AnyObject) {
|
||||
|
||||
markArticles(Set(selectedArticles), statusKey: ArticleStatusKey.read.rawValue, flag: true)
|
||||
markArticles(Set(selectedArticles), statusKey: .read, flag: true)
|
||||
}
|
||||
|
||||
@IBAction func markSelectedArticlesAsUnread(_ sender: AnyObject) {
|
||||
|
||||
markArticles(Set(selectedArticles), statusKey: ArticleStatusKey.read.rawValue, flag: false)
|
||||
markArticles(Set(selectedArticles), statusKey: .read, flag: false)
|
||||
}
|
||||
|
||||
// MARK: Navigation
|
||||
// MARK: - Navigation
|
||||
|
||||
func goToNextUnread() {
|
||||
|
||||
@ -212,7 +212,7 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: Notifications
|
||||
// MARK: - Notifications
|
||||
|
||||
@objc func sidebarSelectionDidChange(_ note: Notification) {
|
||||
|
||||
@ -223,12 +223,12 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
||||
}
|
||||
}
|
||||
|
||||
@objc func articleStatusesDidChange(_ note: Notification) {
|
||||
@objc func statusesDidChange(_ note: Notification) {
|
||||
|
||||
guard let articles = note.appInfo?.articles else {
|
||||
guard let statuses = note.userInfo?[Account.UserInfoKey.statuses] as? Set<ArticleStatus> else {
|
||||
return
|
||||
}
|
||||
reloadCellsForArticles(Array(articles))
|
||||
reloadCellsForArticleIDs(statuses.articleIDs())
|
||||
}
|
||||
|
||||
func fontSizeInDefaultsDidChange() {
|
||||
@ -243,7 +243,7 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: KeyboardDelegate
|
||||
// MARK: - KeyboardDelegate
|
||||
|
||||
func handleKeydownEvent(_ event: NSEvent, sender: AnyObject) -> Bool {
|
||||
|
||||
@ -302,7 +302,7 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
||||
return keyHandled
|
||||
}
|
||||
|
||||
// MARK: Reloading Data
|
||||
// MARK: - Reloading Data
|
||||
|
||||
private func cellForRowView(_ rowView: NSView) -> NSView? {
|
||||
|
||||
@ -314,18 +314,23 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
||||
|
||||
private func reloadCellsForArticles(_ articles: [Article]) {
|
||||
|
||||
let indexes = indexesForArticles(articles)
|
||||
reloadCellsForArticleIDs(Set(articles.articleIDs()))
|
||||
}
|
||||
|
||||
private func reloadCellsForArticleIDs(_ articleIDs: Set<String>) {
|
||||
|
||||
let indexes = indexesForArticleIDs(articleIDs)
|
||||
tableView.reloadData(forRowIndexes: indexes, columnIndexes: NSIndexSet(index: 0) as IndexSet)
|
||||
}
|
||||
|
||||
// MARK: Articles
|
||||
// MARK: - Articles
|
||||
|
||||
private func indexesForArticles(_ articles: [Article]) -> IndexSet {
|
||||
private func indexesForArticleIDs(_ articleIDs: Set<String>) -> IndexSet {
|
||||
|
||||
var indexes = IndexSet()
|
||||
|
||||
articles.forEach { (article) in
|
||||
let oneIndex = rowForArticle(article)
|
||||
articleIDs.forEach { (articleID) in
|
||||
let oneIndex = rowForArticleID(articleID)
|
||||
if oneIndex != NSNotFound {
|
||||
indexes.insert(oneIndex)
|
||||
}
|
||||
@ -351,7 +356,12 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
||||
|
||||
private func rowForArticle(_ article: Article) -> Int {
|
||||
|
||||
if let index = articles.index(where: { $0.articleID == article.articleID }) {
|
||||
return rowForArticleID(article.articleID)
|
||||
}
|
||||
|
||||
private func rowForArticleID(_ articleID: String) -> Int {
|
||||
|
||||
if let index = articles.index(where: { $0.articleID == articleID }) {
|
||||
return index
|
||||
}
|
||||
|
||||
@ -409,7 +419,7 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Cell Configuring
|
||||
// MARK: - Cell Configuring
|
||||
|
||||
private func calculateRowHeight() -> CGFloat {
|
||||
|
||||
@ -435,7 +445,7 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
||||
cell.cellData = emptyCellData
|
||||
}
|
||||
|
||||
// MARK: NSTableViewDataSource
|
||||
// MARK: - NSTableViewDataSource
|
||||
|
||||
func numberOfRows(in tableView: NSTableView) -> Int {
|
||||
|
||||
@ -447,7 +457,7 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
||||
return articleAtRow(row)
|
||||
}
|
||||
|
||||
// MARK: NSTableViewDelegate
|
||||
// MARK: - NSTableViewDelegate
|
||||
|
||||
func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
|
||||
|
||||
@ -495,7 +505,7 @@ class TimelineViewController: NSViewController, NSTableViewDelegate, NSTableView
|
||||
|
||||
if let selectedArticle = articleAtRow(selectedRow) {
|
||||
if (!selectedArticle.status.read) {
|
||||
markArticles(Set([selectedArticle]), statusKey: ArticleStatusKey.read.rawValue, flag: true)
|
||||
markArticles(Set([selectedArticle]), statusKey: .read, flag: true)
|
||||
}
|
||||
postTimelineSelectionDidChangeNotification(selectedArticle)
|
||||
}
|
||||
@ -536,6 +546,8 @@ private extension TimelineViewController {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSTableView extension
|
||||
|
||||
private extension NSTableView {
|
||||
|
||||
func scrollTo(row: Int) {
|
||||
@ -593,3 +605,4 @@ private extension NSTableView {
|
||||
visibleRowViews()?.forEach { $0.invalidateGridRect() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,8 @@ public extension Notification.Name {
|
||||
public static let AccountRefreshDidFinish = Notification.Name(rawValue: "AccountRefreshDidFinish")
|
||||
public static let AccountRefreshProgressDidChange = Notification.Name(rawValue: "AccountRefreshProgressDidChange")
|
||||
public static let AccountDidDownloadArticles = Notification.Name(rawValue: "AccountDidDownloadArticles")
|
||||
|
||||
public static let StatusesDidChange = Notification.Name(rawValue: "StatusesDidChange")
|
||||
}
|
||||
|
||||
public enum AccountType: Int {
|
||||
@ -34,9 +36,10 @@ public enum AccountType: Int {
|
||||
|
||||
public final class Account: DisplayNameProvider, Container, Hashable {
|
||||
|
||||
public struct UserInfoKey { // Used by AccountDidDownloadArticles.
|
||||
public static let newArticles = "newArticles"
|
||||
public static let updatedArticles = "updatedArticles"
|
||||
public struct UserInfoKey {
|
||||
public static let newArticles = "newArticles" // AccountDidDownloadArticles
|
||||
public static let updatedArticles = "updatedArticles" // AccountDidDownloadArticles
|
||||
public static let statuses = "statuses" // ArticleStatusesDidChange
|
||||
}
|
||||
|
||||
public let accountID: String
|
||||
@ -151,9 +154,11 @@ public final class Account: DisplayNameProvider, Container, Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
public func markArticles(_ articles: Set<Article>, statusKey: String, flag: Bool) {
|
||||
public func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) {
|
||||
|
||||
database.mark(articles, statusKey: statusKey, flag: flag)
|
||||
if let updatedStatuses = database.mark(articles, statusKey: statusKey, flag: flag) {
|
||||
NotificationCenter.default.post(name: .StatusesDidChange, object: self, userInfo: [UserInfoKey.statuses: updatedStatuses])
|
||||
}
|
||||
}
|
||||
|
||||
public func ensureFolder(with name: String) -> Folder? {
|
||||
|
@ -71,4 +71,18 @@ public struct Article: Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
public extension Set where Element == Article {
|
||||
|
||||
public func articleIDs() -> Set<String> {
|
||||
|
||||
return Set<String>(map { $0.articleID })
|
||||
}
|
||||
}
|
||||
|
||||
public extension Array where Element == Article {
|
||||
|
||||
public func articleIDs() -> [String] {
|
||||
|
||||
return map { $0.articleID }
|
||||
}
|
||||
}
|
||||
|
@ -14,15 +14,14 @@ import Foundation
|
||||
// Which is safe, because at creation time it’t not yet shared,
|
||||
// and it won’t be mutated ever on a background thread.
|
||||
|
||||
public enum ArticleStatusKey: String {
|
||||
|
||||
case read = "read"
|
||||
case starred = "starred"
|
||||
case userDeleted = "userDeleted"
|
||||
}
|
||||
|
||||
public final class ArticleStatus: Hashable {
|
||||
|
||||
public enum Key: String {
|
||||
case read = "read"
|
||||
case starred = "starred"
|
||||
case userDeleted = "userDeleted"
|
||||
}
|
||||
|
||||
public let articleID: String
|
||||
public let dateArrived: Date
|
||||
public let hashValue: Int
|
||||
@ -46,32 +45,27 @@ public final class ArticleStatus: Hashable {
|
||||
self.init(articleID: articleID, read: false, starred: false, userDeleted: false, dateArrived: dateArrived)
|
||||
}
|
||||
|
||||
public func boolStatus(forKey key: String) -> Bool {
|
||||
public func boolStatus(forKey key: ArticleStatus.Key) -> Bool {
|
||||
|
||||
if let articleStatusKey = ArticleStatusKey(rawValue: key) {
|
||||
switch articleStatusKey {
|
||||
case .read:
|
||||
return read
|
||||
case .starred:
|
||||
return starred
|
||||
case .userDeleted:
|
||||
return userDeleted
|
||||
}
|
||||
switch key {
|
||||
case .read:
|
||||
return read
|
||||
case .starred:
|
||||
return starred
|
||||
case .userDeleted:
|
||||
return userDeleted
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public func setBoolStatus(_ status: Bool, forKey key: String) {
|
||||
public func setBoolStatus(_ status: Bool, forKey key: ArticleStatus.Key) {
|
||||
|
||||
if let articleStatusKey = ArticleStatusKey(rawValue: key) {
|
||||
switch articleStatusKey {
|
||||
case .read:
|
||||
read = status
|
||||
case .starred:
|
||||
starred = status
|
||||
case .userDeleted:
|
||||
userDeleted = status
|
||||
}
|
||||
switch key {
|
||||
case .read:
|
||||
read = status
|
||||
case .starred:
|
||||
starred = status
|
||||
case .userDeleted:
|
||||
userDeleted = status
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,3 +74,19 @@ public final class ArticleStatus: Hashable {
|
||||
return lhs.hashValue == rhs.hashValue && lhs.articleID == rhs.articleID && lhs.dateArrived == rhs.dateArrived && lhs.read == rhs.read && lhs.starred == rhs.starred && lhs.userDeleted == rhs.userDeleted
|
||||
}
|
||||
}
|
||||
|
||||
public extension Set where Element == ArticleStatus {
|
||||
|
||||
public func articleIDs() -> Set<String> {
|
||||
|
||||
return Set<String>(map { $0.articleID })
|
||||
}
|
||||
}
|
||||
|
||||
public extension Array where Element == ArticleStatus {
|
||||
|
||||
public func articleIDs() -> [String] {
|
||||
|
||||
return map { $0.articleID }
|
||||
}
|
||||
}
|
||||
|
@ -144,9 +144,9 @@ final class ArticlesTable: DatabaseTable {
|
||||
|
||||
// MARK: Status
|
||||
|
||||
func mark(_ articles: Set<Article>, _ statusKey: String, _ flag: Bool) {
|
||||
func mark(_ articles: Set<Article>, _ statusKey: ArticleStatus.Key, _ flag: Bool) -> Set<ArticleStatus>? {
|
||||
|
||||
statusesTable.mark(articles.statuses(), statusKey, flag)
|
||||
return statusesTable.mark(articles.statuses(), statusKey, flag)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,9 +70,9 @@ public final class Database {
|
||||
|
||||
// MARK: - Status
|
||||
|
||||
public func mark(_ articles: Set<Article>, statusKey: String, flag: Bool) {
|
||||
public func mark(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<ArticleStatus>? {
|
||||
|
||||
articlesTable.mark(articles, statusKey, flag)
|
||||
return articlesTable.mark(articles, statusKey, flag)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,11 +118,6 @@ extension Article: DatabaseObject {
|
||||
|
||||
extension Set where Element == Article {
|
||||
|
||||
func articleIDs() -> Set<String> {
|
||||
|
||||
return Set<String>(map { $0.databaseID })
|
||||
}
|
||||
|
||||
func statuses() -> Set<ArticleStatus> {
|
||||
|
||||
return Set<ArticleStatus>(map { $0.status })
|
||||
|
@ -45,10 +45,3 @@ extension ArticleStatus: DatabaseObject {
|
||||
}
|
||||
}
|
||||
|
||||
extension Set where Element == ArticleStatus {
|
||||
|
||||
func articleIDs() -> Set<String> {
|
||||
|
||||
return Set<String>(map { $0.articleID })
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ final class StatusesTable: DatabaseTable {
|
||||
|
||||
// MARK: Marking
|
||||
|
||||
func mark(_ statuses: Set<ArticleStatus>, _ statusKey: String, _ flag: Bool) {
|
||||
func mark(_ statuses: Set<ArticleStatus>, _ statusKey: ArticleStatus.Key, _ flag: Bool) -> Set<ArticleStatus>? {
|
||||
|
||||
// Sets flag in both memory and in database.
|
||||
|
||||
@ -66,13 +66,14 @@ final class StatusesTable: DatabaseTable {
|
||||
}
|
||||
|
||||
if updatedStatuses.isEmpty {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
let articleIDs = updatedStatuses.articleIDs()
|
||||
|
||||
queue.update { (database) in
|
||||
self.markArticleIDs(articleIDs, statusKey, flag, database)
|
||||
}
|
||||
return updatedStatuses
|
||||
}
|
||||
|
||||
// MARK: Fetching
|
||||
@ -150,9 +151,9 @@ private extension StatusesTable {
|
||||
|
||||
// MARK: Marking
|
||||
|
||||
func markArticleIDs(_ articleIDs: Set<String>, _ statusKey: String, _ flag: Bool, _ database: FMDatabase) {
|
||||
func markArticleIDs(_ articleIDs: Set<String>, _ statusKey: ArticleStatus.Key, _ flag: Bool, _ database: FMDatabase) {
|
||||
|
||||
updateRowsWithValue(NSNumber(value: flag), valueKey: statusKey, whereKey: DatabaseKey.articleID, matches: Array(articleIDs), database: database)
|
||||
updateRowsWithValue(NSNumber(value: flag), valueKey: statusKey.rawValue, whereKey: DatabaseKey.articleID, matches: Array(articleIDs), database: database)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
</editor> -->
|
||||
<title>ToDo</title>
|
||||
<dateCreated>Tue, 12 Sep 2017 20:15:17 GMT</dateCreated>
|
||||
<expansionState>0,1,23,24,27,31,37,45,46,48,63,68</expansionState>
|
||||
<expansionState>0,23,24,27,31,37,45,46,48,63,68</expansionState>
|
||||
<vertScrollState>0</vertScrollState>
|
||||
<windowTop>637</windowTop>
|
||||
<windowLeft>42</windowLeft>
|
||||
@ -15,9 +15,8 @@
|
||||
</head>
|
||||
<body>
|
||||
<outline text="App">
|
||||
<outline text="Mark article as read on selection">
|
||||
<outline text="Update display"/>
|
||||
</outline>
|
||||
<outline text="Make adding a Folder work — accounts don’t show up in Accounts popup"/>
|
||||
<outline text="Update unread count on marking article status"/>
|
||||
<outline text="Update Sparkle"/>
|
||||
<outline text="Use new app icon"/>
|
||||
<outline text="Set -NSApplicationCrashOnExceptions YES"/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user