Do the first parts of showing feed avatars.
This commit is contained in:
parent
1e528ee8b0
commit
dd05a24704
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user