diff --git a/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift b/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift index 814815eff..b6ca525bc 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift @@ -15,14 +15,15 @@ struct TimelineCellData { let text: String let dateString: String let feedName: String - let showFeedName: Bool + let byline: String + let showFeedName: TimelineShowFeedName let iconImage: IconImage? // feed icon, user avatar, or favicon let showIcon: Bool // Make space even when icon is nil let featuredImage: NSImage? // image from within the article let read: Bool let starred: Bool - init(article: Article, showFeedName: Bool, feedName: String?, iconImage: IconImage?, showIcon: Bool, featuredImage: NSImage?) { + init(article: Article, showFeedName: TimelineShowFeedName, feedName: String?, byline: String?, iconImage: IconImage?, showIcon: Bool, featuredImage: NSImage?) { self.title = ArticleStringFormatter.truncatedTitle(article) self.text = ArticleStringFormatter.truncatedSummary(article) @@ -31,10 +32,15 @@ struct TimelineCellData { if let feedName = feedName { self.feedName = ArticleStringFormatter.truncatedFeedName(feedName) - } - else { + } else { self.feedName = "" } + + if let byline = byline { + self.byline = byline + } else { + self.byline = "" + } self.showFeedName = showFeedName @@ -51,7 +57,8 @@ struct TimelineCellData { self.text = "" self.dateString = "" self.feedName = "" - self.showFeedName = false + self.byline = "" + self.showFeedName = .none self.showIcon = false self.iconImage = nil self.featuredImage = nil diff --git a/Mac/MainWindow/Timeline/Cell/TimelineCellLayout.swift b/Mac/MainWindow/Timeline/Cell/TimelineCellLayout.swift index 8dddd1bef..07bb08c9b 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineCellLayout.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineCellLayout.swift @@ -171,7 +171,7 @@ private extension TimelineCellLayout { } static func rectForFeedName(_ textBoxRect: NSRect, _ dateRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect { - if !cellData.showFeedName { + if cellData.showFeedName == .none { return NSZeroRect } diff --git a/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift index 60854027c..eb1e8a79e 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift @@ -248,11 +248,14 @@ private extension TimelineTableCellView { } func updateFeedNameView() { - if cellData.showFeedName { + switch cellData.showFeedName { + case .byline: + showView(feedNameView) + updateTextFieldText(feedNameView, cellData.byline) + case .feed: showView(feedNameView) updateTextFieldText(feedNameView, cellData.feedName) - } - else { + case .none: hideView(feedNameView) } } diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index aaecbe097..4a79c36b8 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -18,6 +18,12 @@ protocol TimelineDelegate: class { func timelineInvalidatedRestorationState(_: TimelineViewController) } +enum TimelineShowFeedName { + case none + case byline + case feed +} + final class TimelineViewController: NSViewController, UndoableCommandRunner, UnreadCountProvider { @IBOutlet var tableView: TimelineTableView! @@ -41,23 +47,11 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr didSet { if !representedObjectArraysAreEqual(oldValue, representedObjects) { unreadCount = 0 - if let representedObjects = representedObjects { - if representedObjects.count == 1 && representedObjects.first is WebFeed { - showFeedNames = false - } - else { - showFeedNames = true - } - } - else { - showFeedNames = false - } selectionDidChange(nil) if showsSearchResults { fetchAndReplaceArticlesAsync() - } - else { + } else { fetchAndReplaceArticlesSync() if articles.count > 0 { tableView.scrollRowToVisible(0) @@ -85,9 +79,11 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr defer { updateUnreadCount() } + if articles == oldValue { return } + if articles.representSameArticlesInSameOrder(as: oldValue) { // When the array is the same — same articles, same order — // but some data in some of the articles may have changed. @@ -96,7 +92,20 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr reloadVisibleCells() return } - updateShowIcons() + + if let representedObjects = representedObjects, representedObjects.count == 1 && representedObjects.first is WebFeed { + showFeedNames = { + for article in articles { + if article.authors?.contains(where: { $0.name != nil }) ?? false { + return .byline + } + } + return .none + }() + } else { + showFeedNames = .feed + } + articleRowMap = [String: Int]() tableView.reloadData() } @@ -117,7 +126,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr private var articleRowMap = [String: Int]() // articleID: rowIndex private var cellAppearance: TimelineCellAppearance! private var cellAppearanceWithIcon: TimelineCellAppearance! - private var showFeedNames = false { + private var showFeedNames: TimelineShowFeedName = .none { didSet { if showFeedNames != oldValue { updateShowIcons() @@ -663,7 +672,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, dateArrived: Date()) let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, webFeedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, datePublished: nil, dateModified: nil, authors: nil, status: status) - let prototypeCellData = TimelineCellData(article: prototypeArticle, showFeedName: true, feedName: "Prototype Feed Name", iconImage: nil, showIcon: false, featuredImage: nil) + let prototypeCellData = TimelineCellData(article: prototypeArticle, showFeedName: .feed, feedName: "Prototype Feed Name", byline: nil, iconImage: nil, showIcon: false, featuredImage: nil) let height = TimelineCellLayout.height(for: 100, cellData: prototypeCellData, appearance: cellAppearance) return height } @@ -810,7 +819,7 @@ extension TimelineViewController: NSTableViewDelegate { private func configureTimelineCell(_ cell: TimelineTableCellView, article: Article) { cell.objectValue = article let iconImage = article.iconImage() - cell.cellData = TimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.webFeed?.nameForDisplay, iconImage: iconImage, showIcon: showIcons, featuredImage: nil) + cell.cellData = TimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.webFeed?.nameForDisplay, byline: article.byline(), iconImage: iconImage, showIcon: showIcons, featuredImage: nil) } private func iconFor(_ article: Article) -> IconImage? { @@ -946,20 +955,20 @@ private extension TimelineViewController { } func updateShowIcons() { - if showFeedNames { + if showFeedNames != .none { self.showIcons = true return } for article in articles { if let authors = article.authors { - for author in authors { - if author.avatarURL != nil { - self.showIcons = true - return + for author in authors { + if author.avatarURL != nil { + self.showIcons = true + return + } } } - } } self.showIcons = false diff --git a/iOS/MasterTimeline/Cell/MasterTimelineAccessibilityCellLayout.swift b/iOS/MasterTimeline/Cell/MasterTimelineAccessibilityCellLayout.swift index 60c8355f3..7441e5ae8 100644 --- a/iOS/MasterTimeline/Cell/MasterTimelineAccessibilityCellLayout.swift +++ b/iOS/MasterTimeline/Cell/MasterTimelineAccessibilityCellLayout.swift @@ -55,7 +55,7 @@ struct MasterTimelineAccessibilityCellLayout: MasterTimelineCellLayout { currentPoint.y = [self.titleRect, self.summaryRect].maxY() - if cellData.showFeedName { + if cellData.showFeedName != .none { self.feedNameRect = MasterTimelineAccessibilityCellLayout.rectForFeedName(cellData, currentPoint, textAreaWidth) currentPoint.y = self.feedNameRect.maxY } else { diff --git a/iOS/MasterTimeline/Cell/MasterTimelineCellData.swift b/iOS/MasterTimeline/Cell/MasterTimelineCellData.swift index 707cea1f4..8996654f8 100644 --- a/iOS/MasterTimeline/Cell/MasterTimelineCellData.swift +++ b/iOS/MasterTimeline/Cell/MasterTimelineCellData.swift @@ -15,7 +15,8 @@ struct MasterTimelineCellData { let summary: String let dateString: String let feedName: String - let showFeedName: Bool + let byline: String + let showFeedName: ShowFeedName let iconImage: IconImage? // feed icon, user avatar, or favicon let showIcon: Bool // Make space even when icon is nil let featuredImage: UIImage? // image from within the article @@ -24,7 +25,7 @@ struct MasterTimelineCellData { let numberOfLines: Int let iconSize: IconSize - init(article: Article, showFeedName: Bool, feedName: String?, iconImage: IconImage?, showIcon: Bool, featuredImage: UIImage?, numberOfLines: Int, iconSize: IconSize) { + init(article: Article, showFeedName: ShowFeedName, feedName: String?, byline: String?, iconImage: IconImage?, showIcon: Bool, featuredImage: UIImage?, numberOfLines: Int, iconSize: IconSize) { self.title = ArticleStringFormatter.truncatedTitle(article) self.summary = ArticleStringFormatter.truncatedSummary(article) @@ -37,6 +38,12 @@ struct MasterTimelineCellData { else { self.feedName = "" } + + if let byline = byline { + self.byline = byline + } else { + self.byline = "" + } self.showFeedName = showFeedName @@ -56,7 +63,8 @@ struct MasterTimelineCellData { self.summary = "" self.dateString = "" self.feedName = "" - self.showFeedName = false + self.byline = "" + self.showFeedName = .none self.showIcon = false self.iconImage = nil self.featuredImage = nil diff --git a/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift b/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift index aa964b218..863ee7631 100644 --- a/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift +++ b/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift @@ -172,12 +172,18 @@ private extension MasterTimelineTableViewCell { } func updateFeedNameView() { - if cellData.showFeedName { + switch cellData.showFeedName { + case .feed: showView(feedNameView) feedNameView.font = MasterTimelineDefaultCellLayout.feedNameFont feedNameView.textColor = secondaryLabelColor updateTextFieldText(feedNameView, cellData.feedName) - } else { + case .byline: + showView(feedNameView) + feedNameView.font = MasterTimelineDefaultCellLayout.feedNameFont + feedNameView.textColor = secondaryLabelColor + updateTextFieldText(feedNameView, cellData.byline) + case .none: hideView(feedNameView) } } diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index fdd148c81..11fd51a17 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -489,7 +489,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, dateArrived: Date()) let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, webFeedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, datePublished: nil, dateModified: nil, authors: nil, status: status) - let prototypeCellData = MasterTimelineCellData(article: prototypeArticle, showFeedName: true, feedName: "Prototype Feed Name", iconImage: nil, showIcon: false, featuredImage: nil, numberOfLines: numberOfTextLines, iconSize: iconSize) + let prototypeCellData = MasterTimelineCellData(article: prototypeArticle, showFeedName: .feed, feedName: "Prototype Feed Name", byline: nil, iconImage: nil, showIcon: false, featuredImage: nil, numberOfLines: numberOfTextLines, iconSize: iconSize) if UIApplication.shared.preferredContentSizeCategory.isAccessibilityCategory { let layout = MasterTimelineAccessibilityCellLayout(width: tableView.bounds.width, insets: tableView.safeAreaInsets, cellData: prototypeCellData) @@ -649,7 +649,7 @@ private extension MasterTimelineViewController { let showFeedNames = coordinator.showFeedNames let showIcon = coordinator.showIcons && iconImage != nil - cell.cellData = MasterTimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.webFeed?.nameForDisplay, iconImage: iconImage, showIcon: showIcon, featuredImage: featuredImage, numberOfLines: numberOfTextLines, iconSize: iconSize) + cell.cellData = MasterTimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.webFeed?.nameForDisplay, byline: article.byline(), iconImage: iconImage, showIcon: showIcon, featuredImage: featuredImage, numberOfLines: numberOfTextLines, iconSize: iconSize) } diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index fb2ad7e27..d384f53ee 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -23,6 +23,12 @@ enum SearchScope: Int { case global = 1 } +enum ShowFeedName { + case none + case byline + case feed +} + class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { var undoableCommands = [UndoableCommand]() @@ -159,7 +165,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { var timelineMiddleIndexPath: IndexPath? - private(set) var showFeedNames = false + private(set) var showFeedNames = ShowFeedName.none private(set) var showIcons = false var prevFeedIndexPath: IndexPath? { @@ -1454,12 +1460,19 @@ private extension SceneCoordinator { func updateShowNamesAndIcons() { if timelineFeed is WebFeed { - showFeedNames = false + showFeedNames = { + for article in articles { + if article.authors?.contains(where: { $0.name != nil }) ?? false { + return .byline + } + } + return .none + }() } else { - showFeedNames = true + showFeedNames = .feed } - if showFeedNames { + if showFeedNames == .feed { self.showIcons = true return } diff --git a/iOS/Settings/TimelinePreviewTableViewController.swift b/iOS/Settings/TimelinePreviewTableViewController.swift index 8d3fa87b9..525c560de 100644 --- a/iOS/Settings/TimelinePreviewTableViewController.swift +++ b/iOS/Settings/TimelinePreviewTableViewController.swift @@ -71,7 +71,7 @@ private extension TimelinePreviewTableViewController { let iconImage = IconImage(AppAssets.faviconTemplateImage.withTintColor(AppAssets.secondaryAccentColor)) - return MasterTimelineCellData(article: prototypeArticle, showFeedName: true, feedName: "Feed Name", iconImage: iconImage, showIcon: true, featuredImage: nil, numberOfLines: AppDefaults.timelineNumberOfLines, iconSize: AppDefaults.timelineIconSize) + return MasterTimelineCellData(article: prototypeArticle, showFeedName: .feed, feedName: "Feed Name", byline: nil, iconImage: iconImage, showIcon: true, featuredImage: nil, numberOfLines: AppDefaults.timelineNumberOfLines, iconSize: AppDefaults.timelineIconSize) } }