From dd05a247047d40b9ca70c6a9c5a3145841f4085f Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Tue, 28 Nov 2017 21:39:09 -0800 Subject: [PATCH] Do the first parts of showing feed avatars. --- .../Images/FeaturedImageDownloader.swift | 8 ++++ Evergreen/Images/FeedIconDownloader.swift | 23 +++++++--- .../Cell/TimelineCellAppearance.swift | 44 +++++++++++-------- .../Timeline/Cell/TimelineCellLayout.swift | 27 +++++++----- .../Timeline/Cell/TimelineTableCellView.swift | 15 +++++++ Evergreen/Resources/DB5.plist | 8 ++-- 6 files changed, 87 insertions(+), 38 deletions(-) diff --git a/Evergreen/Images/FeaturedImageDownloader.swift b/Evergreen/Images/FeaturedImageDownloader.swift index 3a043b811..3b429c3fb 100644 --- a/Evergreen/Images/FeaturedImageDownloader.swift +++ b/Evergreen/Images/FeaturedImageDownloader.swift @@ -15,6 +15,7 @@ final class FeaturedImageDownloader { private let imageDownloader: ImageDownloader private var articleURLToFeaturedImageURLCache = [String: String]() private var articleURLsWithNoFeaturedImage = Set() + private var urlsInProgress = Set() init(imageDownloader: ImageDownloader) { @@ -66,8 +67,15 @@ private extension FeaturedImageDownloader { func findFeaturedImageURL(for articleURL: String) { + guard !urlsInProgress.contains(articleURL) else { + return + } + urlsInProgress.insert(articleURL) + HTMLMetadataDownloader.downloadMetadata(for: articleURL) { (metadata) in + self.urlsInProgress.remove(articleURL) + guard let metadata = metadata else { return } diff --git a/Evergreen/Images/FeedIconDownloader.swift b/Evergreen/Images/FeedIconDownloader.swift index b2504f048..25989956c 100644 --- a/Evergreen/Images/FeedIconDownloader.swift +++ b/Evergreen/Images/FeedIconDownloader.swift @@ -16,7 +16,8 @@ public final class FeedIconDownloader { private let imageDownloader: ImageDownloader private var homePageToIconURLCache = [String: String]() private var homePagesWithNoIconURL = Set() - private var homePageDownloadsInProgress = Set() + private var urlsInProgress = Set() + private var cache = [Feed: NSImage]() init(imageDownloader: ImageDownloader) { @@ -25,12 +26,22 @@ public final class FeedIconDownloader { func icon(for feed: Feed) -> NSImage? { + if let cachedImage = cache[feed] { + return cachedImage + } + if let iconURL = feed.iconURL { - return icon(forURL: iconURL) + if let image = icon(forURL: iconURL) { + cache[feed] = image + return image + } } if let homePageURL = feed.homePageURL { - return icon(forHomePageURL: homePageURL) + if let image = icon(forHomePageURL: homePageURL) { + cache[feed] = image + return image + } } return nil @@ -71,14 +82,14 @@ private extension FeedIconDownloader { func findIconURLForHomePageURL(_ homePageURL: String) { - guard !homePageDownloadsInProgress.contains(homePageURL) else { + guard !urlsInProgress.contains(homePageURL) else { return } - homePageDownloadsInProgress.insert(homePageURL) + urlsInProgress.insert(homePageURL) HTMLMetadataDownloader.downloadMetadata(for: homePageURL) { (metadata) in - self.homePageDownloadsInProgress.remove(homePageURL) + self.urlsInProgress.remove(homePageURL) guard let metadata = metadata else { return } diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellAppearance.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellAppearance.swift index 20cef7ffb..588b0ad33 100644 --- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellAppearance.swift +++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellAppearance.swift @@ -36,36 +36,44 @@ struct TimelineCellAppearance { let boxLeftMargin: CGFloat let gridColor: NSColor - + + let avatarSize: NSSize + let avatarMarginRight: CGFloat + let avatarAdjustmentTop: CGFloat + init(theme: VSTheme, fontSize: FontSize) { let actualFontSize = AppDefaults.actualFontSize(for: fontSize) - cellPadding = theme.edgeInsets(forKey: "MainWindow.Timeline.cell.padding") + self.cellPadding = theme.edgeInsets(forKey: "MainWindow.Timeline.cell.padding") - feedNameColor = theme.color(forKey: "MainWindow.Timeline.cell.feedNameColor") - feedNameFont = NSFont.systemFont(ofSize: actualFontSize) - faviconFeedNameSpacing = theme.float(forKey: "MainWindow.Timeline.cell.faviconFeedNameSpacing") + self.feedNameColor = theme.color(forKey: "MainWindow.Timeline.cell.feedNameColor") + self.feedNameFont = NSFont.systemFont(ofSize: actualFontSize) + self.faviconFeedNameSpacing = theme.float(forKey: "MainWindow.Timeline.cell.faviconFeedNameSpacing") - dateColor = theme.color(forKey: "MainWindow.Timeline.cell.dateColor") + self.dateColor = theme.color(forKey: "MainWindow.Timeline.cell.dateColor") let actualDateFontSize = AppDefaults.actualFontSize(for: fontSize) - dateFont = NSFont.systemFont(ofSize: actualDateFontSize) - dateMarginLeft = theme.float(forKey: "MainWindow.Timeline.cell.dateMarginLeft") + self.dateFont = NSFont.systemFont(ofSize: actualDateFontSize) + self.dateMarginLeft = theme.float(forKey: "MainWindow.Timeline.cell.dateMarginLeft") - titleColor = theme.color(forKey: "MainWindow.Timeline.cell.titleColor") - titleFont = NSFont.systemFont(ofSize: actualFontSize, weight: NSFont.Weight.bold) - titleBottomMargin = theme.float(forKey: "MainWindow.Timeline.cell.titleMarginBottom") + self.titleColor = theme.color(forKey: "MainWindow.Timeline.cell.titleColor") + self.titleFont = NSFont.systemFont(ofSize: actualFontSize, weight: NSFont.Weight.bold) + self.titleBottomMargin = theme.float(forKey: "MainWindow.Timeline.cell.titleMarginBottom") - textColor = theme.color(forKey: "MainWindow.Timeline.cell.textColor") - textFont = NSFont.systemFont(ofSize: actualFontSize) + self.textColor = theme.color(forKey: "MainWindow.Timeline.cell.textColor") + self.textFont = NSFont.systemFont(ofSize: actualFontSize) - unreadCircleColor = theme.color(forKey: "MainWindow.Timeline.cell.unreadCircleColor") - unreadCircleDimension = theme.float(forKey: "MainWindow.Timeline.cell.unreadCircleDimension") - unreadCircleMarginRight = theme.float(forKey: "MainWindow.Timeline.cell.unreadCircleMarginRight") + self.unreadCircleColor = theme.color(forKey: "MainWindow.Timeline.cell.unreadCircleColor") + self.unreadCircleDimension = theme.float(forKey: "MainWindow.Timeline.cell.unreadCircleDimension") + self.unreadCircleMarginRight = theme.float(forKey: "MainWindow.Timeline.cell.unreadCircleMarginRight") - boxLeftMargin = cellPadding.left + unreadCircleDimension + unreadCircleMarginRight + self.gridColor = theme.colorWithAlpha(forKey: "MainWindow.Timeline.gridColor") + + self.avatarSize = theme.size(forKey: "MainWindow.Timeline.cell.avatar") + self.avatarMarginRight = theme.float(forKey: "MainWindow.Timeline.cell.avatarMarginRight") + self.avatarAdjustmentTop = theme.float(forKey: "MainWindow.Timeline.cell.avatarAdjustmentTop") - gridColor = theme.colorWithAlpha(forKey: "MainWindow.Timeline.gridColor") + self.boxLeftMargin = self.cellPadding.left + self.unreadCircleDimension + self.unreadCircleMarginRight + self.avatarSize.width + self.avatarMarginRight } } diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift index 8d813fb04..a981589ef 100644 --- a/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift +++ b/Evergreen/MainWindow/Timeline/Cell/TimelineCellLayout.swift @@ -43,7 +43,10 @@ struct TimelineCellLayout { height = NSMaxY(feedNameRect) } height = height + paddingBottom - + + let heightOfImage = avatarImageRect.maxY + paddingBottom + height = max(height, heightOfImage) + self.height = height } } @@ -59,11 +62,8 @@ private func rectForDate(_ cellData: TimelineCellData, _ width: CGFloat, _ appea r.origin.y = NSMaxY(titleRect) + appearance.titleBottomMargin r.origin.x = appearance.boxLeftMargin - r.size.width = width - (r.origin.x + appearance.cellPadding.right) - if r.size.width < 15 { - return NSZeroRect - } - + r.size.width = max(width - (r.origin.x + appearance.cellPadding.right), 0.0) + return r } @@ -118,7 +118,8 @@ private func rectsForTitle(_ cellData: TimelineCellData, _ width: CGFloat, _ app let measurements = renderer.measurements(forWidth: textWidth) r.size = NSSize(width: textWidth, height: CGFloat(measurements.height)) - + r.size.width = max(r.size.width, 0.0) + var rline1 = r rline1.size.height = CGFloat(measurements.heightOfFirstLine) @@ -139,10 +140,14 @@ private func rectForUnreadIndicator(_ cellData: TimelineCellData, _ appearance: return r } -private func rectForAvatar(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ unreadIndictarRect: NSRect) -> NSRect { +private func rectForAvatar(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ titleLine1Rect: NSRect) -> NSRect { - // TODO - return NSRect.zero + var r = NSRect.zero + r.size = appearance.avatarSize + r.origin.x = appearance.cellPadding.left + appearance.unreadCircleDimension + appearance.unreadCircleMarginRight + r.origin.y = titleLine1Rect.minY + appearance.avatarAdjustmentTop + + return r } func timelineCellLayout(_ width: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance) -> TimelineCellLayout { @@ -153,7 +158,7 @@ func timelineCellLayout(_ width: CGFloat, cellData: TimelineCellData, appearance let feedNameRect = rectForFeedName(cellData, width, appearance, titleRect) let faviconRect = rectForFavicon(cellData, appearance, feedNameRect) let unreadIndicatorRect = rectForUnreadIndicator(cellData, appearance, titleLine1Rect) - let avatarImageRect = rectForAvatar(cellData, appearance, unreadIndicatorRect) + let avatarImageRect = rectForAvatar(cellData, appearance, titleLine1Rect) return TimelineCellLayout(width: width, faviconRect: faviconRect, feedNameRect: feedNameRect, dateRect: dateRect, titleRect: titleRect, unreadIndicatorRect: unreadIndicatorRect, avatarImageRect: avatarImageRect, paddingBottom: appearance.cellPadding.bottom) } diff --git a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift index 6bc4a1e99..4a1e28c67 100644 --- a/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift +++ b/Evergreen/MainWindow/Timeline/Cell/TimelineTableCellView.swift @@ -122,6 +122,7 @@ class TimelineTableCellView: NSTableCellView { unreadIndicatorView.rs_setFrameIfNotEqual(layoutRects.unreadIndicatorRect) dateView.rs_setFrameIfNotEqual(layoutRects.dateRect) feedNameView.rs_setFrameIfNotEqual(layoutRects.feedNameRect) + avatarImageView.rs_setFrameIfNotEqual(layoutRects.avatarImageRect) } override func updateLayer() { @@ -173,12 +174,26 @@ class TimelineTableCellView: NSTableCellView { } } + private func updateAvatar() { + + if let image = cellData.avatar { + if avatarImageView.image !== image { + avatarImageView.image = image + } + avatarImageView.isHidden = false + } + else { + avatarImageView.isHidden = true + } + } + private func updateSubviews() { updateTitleView() updateDateView() updateFeedNameView() updateUnreadIndicator() + updateAvatar() } private func updateAppearance() { diff --git a/Evergreen/Resources/DB5.plist b/Evergreen/Resources/DB5.plist index 8c6d54b5e..8c0d00c40 100644 --- a/Evergreen/Resources/DB5.plist +++ b/Evergreen/Resources/DB5.plist @@ -94,11 +94,13 @@ unreadCircleMarginRight 8 avatarHeight - 64 + 42 avatarWidth - 64 + 42 avatarMarginRight - 8 + 8 + avatarAdjustmentTop + 4