Do the first parts of showing feed avatars.

This commit is contained in:
Brent Simmons 2017-11-28 21:39:09 -08:00
parent 1e528ee8b0
commit dd05a24704
6 changed files with 87 additions and 38 deletions

View File

@ -15,6 +15,7 @@ final class FeaturedImageDownloader {
private let imageDownloader: ImageDownloader private let imageDownloader: ImageDownloader
private var articleURLToFeaturedImageURLCache = [String: String]() private var articleURLToFeaturedImageURLCache = [String: String]()
private var articleURLsWithNoFeaturedImage = Set<String>() private var articleURLsWithNoFeaturedImage = Set<String>()
private var urlsInProgress = Set<String>()
init(imageDownloader: ImageDownloader) { init(imageDownloader: ImageDownloader) {
@ -66,8 +67,15 @@ private extension FeaturedImageDownloader {
func findFeaturedImageURL(for articleURL: String) { func findFeaturedImageURL(for articleURL: String) {
guard !urlsInProgress.contains(articleURL) else {
return
}
urlsInProgress.insert(articleURL)
HTMLMetadataDownloader.downloadMetadata(for: articleURL) { (metadata) in HTMLMetadataDownloader.downloadMetadata(for: articleURL) { (metadata) in
self.urlsInProgress.remove(articleURL)
guard let metadata = metadata else { guard let metadata = metadata else {
return return
} }

View File

@ -16,7 +16,8 @@ public final class FeedIconDownloader {
private let imageDownloader: ImageDownloader private let imageDownloader: ImageDownloader
private var homePageToIconURLCache = [String: String]() private var homePageToIconURLCache = [String: String]()
private var homePagesWithNoIconURL = Set<String>() private var homePagesWithNoIconURL = Set<String>()
private var homePageDownloadsInProgress = Set<String>() private var urlsInProgress = Set<String>()
private var cache = [Feed: NSImage]()
init(imageDownloader: ImageDownloader) { init(imageDownloader: ImageDownloader) {
@ -25,12 +26,22 @@ public final class FeedIconDownloader {
func icon(for feed: Feed) -> NSImage? { func icon(for feed: Feed) -> NSImage? {
if let cachedImage = cache[feed] {
return cachedImage
}
if let iconURL = feed.iconURL { 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 { if let homePageURL = feed.homePageURL {
return icon(forHomePageURL: homePageURL) if let image = icon(forHomePageURL: homePageURL) {
cache[feed] = image
return image
}
} }
return nil return nil
@ -71,14 +82,14 @@ private extension FeedIconDownloader {
func findIconURLForHomePageURL(_ homePageURL: String) { func findIconURLForHomePageURL(_ homePageURL: String) {
guard !homePageDownloadsInProgress.contains(homePageURL) else { guard !urlsInProgress.contains(homePageURL) else {
return return
} }
homePageDownloadsInProgress.insert(homePageURL) urlsInProgress.insert(homePageURL)
HTMLMetadataDownloader.downloadMetadata(for: homePageURL) { (metadata) in HTMLMetadataDownloader.downloadMetadata(for: homePageURL) { (metadata) in
self.homePageDownloadsInProgress.remove(homePageURL) self.urlsInProgress.remove(homePageURL)
guard let metadata = metadata else { guard let metadata = metadata else {
return return
} }

View File

@ -36,36 +36,44 @@ struct TimelineCellAppearance {
let boxLeftMargin: CGFloat let boxLeftMargin: CGFloat
let gridColor: NSColor let gridColor: NSColor
let avatarSize: NSSize
let avatarMarginRight: CGFloat
let avatarAdjustmentTop: CGFloat
init(theme: VSTheme, fontSize: FontSize) { init(theme: VSTheme, fontSize: FontSize) {
let actualFontSize = AppDefaults.actualFontSize(for: 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") self.feedNameColor = theme.color(forKey: "MainWindow.Timeline.cell.feedNameColor")
feedNameFont = NSFont.systemFont(ofSize: actualFontSize) self.feedNameFont = NSFont.systemFont(ofSize: actualFontSize)
faviconFeedNameSpacing = theme.float(forKey: "MainWindow.Timeline.cell.faviconFeedNameSpacing") 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) let actualDateFontSize = AppDefaults.actualFontSize(for: fontSize)
dateFont = NSFont.systemFont(ofSize: actualDateFontSize) self.dateFont = NSFont.systemFont(ofSize: actualDateFontSize)
dateMarginLeft = theme.float(forKey: "MainWindow.Timeline.cell.dateMarginLeft") self.dateMarginLeft = theme.float(forKey: "MainWindow.Timeline.cell.dateMarginLeft")
titleColor = theme.color(forKey: "MainWindow.Timeline.cell.titleColor") self.titleColor = theme.color(forKey: "MainWindow.Timeline.cell.titleColor")
titleFont = NSFont.systemFont(ofSize: actualFontSize, weight: NSFont.Weight.bold) self.titleFont = NSFont.systemFont(ofSize: actualFontSize, weight: NSFont.Weight.bold)
titleBottomMargin = theme.float(forKey: "MainWindow.Timeline.cell.titleMarginBottom") self.titleBottomMargin = theme.float(forKey: "MainWindow.Timeline.cell.titleMarginBottom")
textColor = theme.color(forKey: "MainWindow.Timeline.cell.textColor") self.textColor = theme.color(forKey: "MainWindow.Timeline.cell.textColor")
textFont = NSFont.systemFont(ofSize: actualFontSize) self.textFont = NSFont.systemFont(ofSize: actualFontSize)
unreadCircleColor = theme.color(forKey: "MainWindow.Timeline.cell.unreadCircleColor") self.unreadCircleColor = theme.color(forKey: "MainWindow.Timeline.cell.unreadCircleColor")
unreadCircleDimension = theme.float(forKey: "MainWindow.Timeline.cell.unreadCircleDimension") self.unreadCircleDimension = theme.float(forKey: "MainWindow.Timeline.cell.unreadCircleDimension")
unreadCircleMarginRight = theme.float(forKey: "MainWindow.Timeline.cell.unreadCircleMarginRight") 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
} }
} }

View File

@ -43,7 +43,10 @@ struct TimelineCellLayout {
height = NSMaxY(feedNameRect) height = NSMaxY(feedNameRect)
} }
height = height + paddingBottom height = height + paddingBottom
let heightOfImage = avatarImageRect.maxY + paddingBottom
height = max(height, heightOfImage)
self.height = height self.height = height
} }
} }
@ -59,11 +62,8 @@ private func rectForDate(_ cellData: TimelineCellData, _ width: CGFloat, _ appea
r.origin.y = NSMaxY(titleRect) + appearance.titleBottomMargin r.origin.y = NSMaxY(titleRect) + appearance.titleBottomMargin
r.origin.x = appearance.boxLeftMargin r.origin.x = appearance.boxLeftMargin
r.size.width = width - (r.origin.x + appearance.cellPadding.right) r.size.width = max(width - (r.origin.x + appearance.cellPadding.right), 0.0)
if r.size.width < 15 {
return NSZeroRect
}
return r return r
} }
@ -118,7 +118,8 @@ private func rectsForTitle(_ cellData: TimelineCellData, _ width: CGFloat, _ app
let measurements = renderer.measurements(forWidth: textWidth) let measurements = renderer.measurements(forWidth: textWidth)
r.size = NSSize(width: textWidth, height: CGFloat(measurements.height)) r.size = NSSize(width: textWidth, height: CGFloat(measurements.height))
r.size.width = max(r.size.width, 0.0)
var rline1 = r var rline1 = r
rline1.size.height = CGFloat(measurements.heightOfFirstLine) rline1.size.height = CGFloat(measurements.heightOfFirstLine)
@ -139,10 +140,14 @@ private func rectForUnreadIndicator(_ cellData: TimelineCellData, _ appearance:
return r return r
} }
private func rectForAvatar(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ unreadIndictarRect: NSRect) -> NSRect { private func rectForAvatar(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ titleLine1Rect: NSRect) -> NSRect {
// TODO var r = NSRect.zero
return 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 { 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 feedNameRect = rectForFeedName(cellData, width, appearance, titleRect)
let faviconRect = rectForFavicon(cellData, appearance, feedNameRect) let faviconRect = rectForFavicon(cellData, appearance, feedNameRect)
let unreadIndicatorRect = rectForUnreadIndicator(cellData, appearance, titleLine1Rect) 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) return TimelineCellLayout(width: width, faviconRect: faviconRect, feedNameRect: feedNameRect, dateRect: dateRect, titleRect: titleRect, unreadIndicatorRect: unreadIndicatorRect, avatarImageRect: avatarImageRect, paddingBottom: appearance.cellPadding.bottom)
} }

View File

@ -122,6 +122,7 @@ class TimelineTableCellView: NSTableCellView {
unreadIndicatorView.rs_setFrameIfNotEqual(layoutRects.unreadIndicatorRect) unreadIndicatorView.rs_setFrameIfNotEqual(layoutRects.unreadIndicatorRect)
dateView.rs_setFrameIfNotEqual(layoutRects.dateRect) dateView.rs_setFrameIfNotEqual(layoutRects.dateRect)
feedNameView.rs_setFrameIfNotEqual(layoutRects.feedNameRect) feedNameView.rs_setFrameIfNotEqual(layoutRects.feedNameRect)
avatarImageView.rs_setFrameIfNotEqual(layoutRects.avatarImageRect)
} }
override func updateLayer() { 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() { private func updateSubviews() {
updateTitleView() updateTitleView()
updateDateView() updateDateView()
updateFeedNameView() updateFeedNameView()
updateUnreadIndicator() updateUnreadIndicator()
updateAvatar()
} }
private func updateAppearance() { private func updateAppearance() {

View File

@ -94,11 +94,13 @@
<key>unreadCircleMarginRight</key> <key>unreadCircleMarginRight</key>
<integer>8</integer> <integer>8</integer>
<key>avatarHeight</key> <key>avatarHeight</key>
<string>64</string> <integer>42</integer>
<key>avatarWidth</key> <key>avatarWidth</key>
<string>64</string> <integer>42</integer>
<key>avatarMarginRight</key> <key>avatarMarginRight</key>
<string>8</string> <integer>8</integer>
<key>avatarAdjustmentTop</key>
<integer>4</integer>
</dict> </dict>
</dict> </dict>
</dict> </dict>