Add IconImage to encapsulate our icon processing logic
This commit is contained in:
parent
05e0e34f6b
commit
560f36621f
@ -16,10 +16,10 @@ extension NSImage.Name {
|
||||
|
||||
struct AppAssets {
|
||||
|
||||
static var genericFeedImage: RSImage? = {
|
||||
static var genericFeedImage: IconImage? = {
|
||||
let path = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/BookmarkIcon.icns"
|
||||
let image = RSImage(contentsOfFile: path)
|
||||
return image
|
||||
return image != nil ? IconImage(image!) : nil
|
||||
}()
|
||||
|
||||
static var timelineStar: RSImage! = {
|
||||
@ -70,28 +70,32 @@ struct AppAssets {
|
||||
return RSImage(named: "faviconTemplateImage")!
|
||||
}()
|
||||
|
||||
static var avatarLightBackgroundColor: NSColor = {
|
||||
return NSColor(named: NSColor.Name("avatarLightBackgroundColor"))!
|
||||
static var iconLightBackgroundColor: NSColor = {
|
||||
return NSColor(named: NSColor.Name("iconLightBackgroundColor"))!
|
||||
}()
|
||||
|
||||
static var avatarDarkBackgroundColor: NSColor = {
|
||||
return NSColor(named: NSColor.Name("avatarDarkBackgroundColor"))!
|
||||
static var iconDarkBackgroundColor: NSColor = {
|
||||
return NSColor(named: NSColor.Name("iconDarkBackgroundColor"))!
|
||||
}()
|
||||
|
||||
static var searchFeedImage: RSImage = {
|
||||
return RSImage(named: NSImage.smartBadgeTemplateName)!
|
||||
static var masterFolderImage: IconImage = {
|
||||
return IconImage(RSImage(named: NSImage.folderName)!)
|
||||
}()
|
||||
|
||||
static var starredFeedImage: RSImage = {
|
||||
return RSImage(named: NSImage.smartBadgeTemplateName)!
|
||||
static var searchFeedImage: IconImage = {
|
||||
return IconImage(RSImage(named: NSImage.smartBadgeTemplateName)!)
|
||||
}()
|
||||
|
||||
static var todayFeedImage: RSImage = {
|
||||
return RSImage(named: NSImage.smartBadgeTemplateName)!
|
||||
static var starredFeedImage: IconImage = {
|
||||
return IconImage(RSImage(named: NSImage.smartBadgeTemplateName)!)
|
||||
}()
|
||||
|
||||
static var unreadFeedImage: RSImage = {
|
||||
return RSImage(named: NSImage.smartBadgeTemplateName)!
|
||||
static var todayFeedImage: IconImage = {
|
||||
return IconImage(RSImage(named: NSImage.smartBadgeTemplateName)!)
|
||||
}()
|
||||
|
||||
static var unreadFeedImage: IconImage = {
|
||||
return IconImage(RSImage(named: NSImage.smartBadgeTemplateName)!)
|
||||
}()
|
||||
|
||||
static var swipeMarkReadImage: RSImage = {
|
||||
|
@ -107,11 +107,11 @@ private extension FeedInspectorViewController {
|
||||
}
|
||||
|
||||
if let feedIcon = appDelegate.feedIconDownloader.icon(for: feed) {
|
||||
imageView?.image = feedIcon
|
||||
imageView?.image = feedIcon.image
|
||||
return
|
||||
}
|
||||
|
||||
if let favicon = appDelegate.faviconDownloader.favicon(for: feed) {
|
||||
if let favicon = appDelegate.faviconDownloader.favicon(for: feed)?.image {
|
||||
if favicon.size.height < 16.0 && favicon.size.width < 16.0 {
|
||||
favicon.size = NSSize(width: 16, height: 16)
|
||||
}
|
||||
@ -119,7 +119,7 @@ private extension FeedInspectorViewController {
|
||||
return
|
||||
}
|
||||
|
||||
imageView?.image = AppAssets.genericFeedImage
|
||||
imageView?.image = AppAssets.genericFeedImage?.image
|
||||
}
|
||||
|
||||
func updateName() {
|
||||
|
@ -32,7 +32,6 @@ import RSCore
|
||||
}()
|
||||
|
||||
static func customSharingServices(for items: [Any]) -> [NSSharingService] {
|
||||
|
||||
let customServices = sendToCommands.compactMap { (sendToCommand) -> NSSharingService? in
|
||||
|
||||
guard let object = items.first else {
|
||||
@ -42,7 +41,7 @@ import RSCore
|
||||
return nil
|
||||
}
|
||||
|
||||
let image = sendToCommand.image ?? AppAssets.genericFeedImage ?? NSImage()
|
||||
let image = sendToCommand.image ?? AppAssets.genericFeedImage?.image ?? NSImage()
|
||||
return NSSharingService(title: sendToCommand.title, image: image, alternateImage: nil) {
|
||||
sendToCommand.sendObject(object, selectedText: nil)
|
||||
}
|
||||
|
@ -81,8 +81,8 @@ class SidebarCell : NSTableCellView {
|
||||
}()
|
||||
|
||||
private let faviconImageView: NSImageView = {
|
||||
let image = AppAssets.genericFeedImage
|
||||
let imageView = image != nil ? NSImageView(image: image!) : NSImageView(frame: NSRect.zero)
|
||||
let iconImage = AppAssets.genericFeedImage
|
||||
let imageView = iconImage != nil ? NSImageView(image: iconImage!.image) : NSImageView(frame: NSRect.zero)
|
||||
imageView.animates = false
|
||||
imageView.imageAlignment = .alignCenter
|
||||
imageView.imageScaling = .scaleProportionallyDown
|
||||
|
@ -526,14 +526,14 @@ private extension SidebarViewController {
|
||||
}
|
||||
|
||||
func configureFavicon(_ cell: SidebarCell, _ node: Node) {
|
||||
cell.image = imageFor(node)
|
||||
cell.image = imageFor(node)?.image
|
||||
}
|
||||
|
||||
func configureGroupCell(_ cell: NSTableCellView, _ node: Node) {
|
||||
cell.textField?.stringValue = nameFor(node)
|
||||
}
|
||||
|
||||
func imageFor(_ node: Node) -> NSImage? {
|
||||
func imageFor(_ node: Node) -> IconImage? {
|
||||
if let smallIconProvider = node.representedObject as? SmallIconProvider {
|
||||
return smallIconProvider.smallIcon
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import AppKit
|
||||
|
||||
struct TimelineCellAppearance: Equatable {
|
||||
|
||||
let showAvatar: Bool
|
||||
let showIcon: Bool
|
||||
|
||||
let cellPadding = NSEdgeInsets(top: 8.0, left: 18.0, bottom: 10.0, right: 18.0)
|
||||
|
||||
@ -35,15 +35,15 @@ struct TimelineCellAppearance: Equatable {
|
||||
|
||||
let drawsGrid = false
|
||||
|
||||
let avatarSize = NSSize(width: 48, height: 48)
|
||||
let avatarMarginLeft: CGFloat = 8.0
|
||||
let avatarMarginRight: CGFloat = 8.0
|
||||
let avatarAdjustmentTop: CGFloat = 4.0
|
||||
let avatarCornerRadius: CGFloat = 4.0
|
||||
let iconSize = NSSize(width: 48, height: 48)
|
||||
let iconMarginLeft: CGFloat = 8.0
|
||||
let iconMarginRight: CGFloat = 8.0
|
||||
let iconAdjustmentTop: CGFloat = 4.0
|
||||
let iconCornerRadius: CGFloat = 4.0
|
||||
|
||||
let boxLeftMargin: CGFloat
|
||||
|
||||
init(showAvatar: Bool, fontSize: FontSize) {
|
||||
init(showIcon: Bool, fontSize: FontSize) {
|
||||
|
||||
let actualFontSize = AppDefaults.actualFontSize(for: fontSize)
|
||||
let smallItemFontSize = actualFontSize //floor(actualFontSize * 0.95)
|
||||
@ -55,7 +55,7 @@ struct TimelineCellAppearance: Equatable {
|
||||
self.textFont = NSFont.systemFont(ofSize: largeItemFontSize)
|
||||
self.textOnlyFont = NSFont.systemFont(ofSize: largeItemFontSize)
|
||||
|
||||
self.showAvatar = showAvatar
|
||||
self.showIcon = showIcon
|
||||
|
||||
let margin = self.cellPadding.left + self.unreadCircleDimension + self.unreadCircleMarginRight
|
||||
self.boxLeftMargin = margin
|
||||
|
@ -16,13 +16,13 @@ struct TimelineCellData {
|
||||
let dateString: String
|
||||
let feedName: String
|
||||
let showFeedName: Bool
|
||||
let avatar: NSImage? // feed icon, user avatar, or favicon
|
||||
let showAvatar: Bool // Make space even when avatar is nil
|
||||
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?, avatar: NSImage?, showAvatar: Bool, featuredImage: NSImage?) {
|
||||
init(article: Article, showFeedName: Bool, feedName: String?, iconImage: IconImage?, showIcon: Bool, featuredImage: NSImage?) {
|
||||
|
||||
self.title = ArticleStringFormatter.truncatedTitle(article)
|
||||
self.text = ArticleStringFormatter.truncatedSummary(article)
|
||||
@ -38,8 +38,8 @@ struct TimelineCellData {
|
||||
|
||||
self.showFeedName = showFeedName
|
||||
|
||||
self.showAvatar = showAvatar
|
||||
self.avatar = avatar
|
||||
self.showIcon = showIcon
|
||||
self.iconImage = iconImage
|
||||
self.featuredImage = featuredImage
|
||||
|
||||
self.read = article.status.read
|
||||
@ -52,8 +52,8 @@ struct TimelineCellData {
|
||||
self.dateString = ""
|
||||
self.feedName = ""
|
||||
self.showFeedName = false
|
||||
self.showAvatar = false
|
||||
self.avatar = nil
|
||||
self.showIcon = false
|
||||
self.iconImage = nil
|
||||
self.featuredImage = nil
|
||||
self.read = true
|
||||
self.starred = false
|
||||
|
@ -21,11 +21,11 @@ struct TimelineCellLayout {
|
||||
let textRect: NSRect
|
||||
let unreadIndicatorRect: NSRect
|
||||
let starRect: NSRect
|
||||
let avatarImageRect: NSRect
|
||||
let iconImageRect: NSRect
|
||||
let separatorRect: NSRect
|
||||
let paddingBottom: CGFloat
|
||||
|
||||
init(width: CGFloat, height: CGFloat, feedNameRect: NSRect, dateRect: NSRect, titleRect: NSRect, numberOfLinesForTitle: Int, summaryRect: NSRect, textRect: NSRect, unreadIndicatorRect: NSRect, starRect: NSRect, avatarImageRect: NSRect, separatorRect: NSRect, paddingBottom: CGFloat) {
|
||||
init(width: CGFloat, height: CGFloat, feedNameRect: NSRect, dateRect: NSRect, titleRect: NSRect, numberOfLinesForTitle: Int, summaryRect: NSRect, textRect: NSRect, unreadIndicatorRect: NSRect, starRect: NSRect, iconImageRect: NSRect, separatorRect: NSRect, paddingBottom: CGFloat) {
|
||||
|
||||
self.width = width
|
||||
self.feedNameRect = feedNameRect
|
||||
@ -36,7 +36,7 @@ struct TimelineCellLayout {
|
||||
self.textRect = textRect
|
||||
self.unreadIndicatorRect = unreadIndicatorRect
|
||||
self.starRect = starRect
|
||||
self.avatarImageRect = avatarImageRect
|
||||
self.iconImageRect = iconImageRect
|
||||
self.separatorRect = separatorRect
|
||||
self.paddingBottom = paddingBottom
|
||||
|
||||
@ -44,16 +44,16 @@ struct TimelineCellLayout {
|
||||
self.height = height
|
||||
}
|
||||
else {
|
||||
self.height = [feedNameRect, dateRect, titleRect, summaryRect, textRect, unreadIndicatorRect, avatarImageRect].maxY() + paddingBottom
|
||||
self.height = [feedNameRect, dateRect, titleRect, summaryRect, textRect, unreadIndicatorRect, iconImageRect].maxY() + paddingBottom
|
||||
}
|
||||
}
|
||||
|
||||
init(width: CGFloat, height: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance, hasAvatar: Bool) {
|
||||
init(width: CGFloat, height: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance, hasIcon: Bool) {
|
||||
|
||||
// If height == 0.0, then height is calculated.
|
||||
|
||||
let showAvatar = cellData.showAvatar
|
||||
var textBoxRect = TimelineCellLayout.rectForTextBox(appearance, cellData, showAvatar, width)
|
||||
let showIcon = cellData.showIcon
|
||||
var textBoxRect = TimelineCellLayout.rectForTextBox(appearance, cellData, showIcon, width)
|
||||
|
||||
let (titleRect, numberOfLinesForTitle) = TimelineCellLayout.rectForTitle(textBoxRect, appearance, cellData)
|
||||
let summaryRect = numberOfLinesForTitle > 0 ? TimelineCellLayout.rectForSummary(textBoxRect, titleRect, numberOfLinesForTitle, appearance, cellData) : NSRect.zero
|
||||
@ -72,19 +72,19 @@ struct TimelineCellLayout {
|
||||
let feedNameRect = TimelineCellLayout.rectForFeedName(textBoxRect, dateRect, appearance, cellData)
|
||||
|
||||
textBoxRect.size.height = ceil([titleRect, summaryRect, textRect, dateRect, feedNameRect].maxY() - textBoxRect.origin.y)
|
||||
let avatarImageRect = TimelineCellLayout.rectForAvatar(cellData, appearance, showAvatar, textBoxRect, width, height)
|
||||
let iconImageRect = TimelineCellLayout.rectForIcon(cellData, appearance, showIcon, textBoxRect, width, height)
|
||||
let unreadIndicatorRect = TimelineCellLayout.rectForUnreadIndicator(appearance, textBoxRect)
|
||||
let starRect = TimelineCellLayout.rectForStar(appearance, unreadIndicatorRect)
|
||||
let separatorRect = TimelineCellLayout.rectForSeparator(cellData, appearance, showAvatar ? avatarImageRect : titleRect, width, height)
|
||||
let separatorRect = TimelineCellLayout.rectForSeparator(cellData, appearance, showIcon ? iconImageRect : titleRect, width, height)
|
||||
|
||||
let paddingBottom = appearance.cellPadding.bottom
|
||||
|
||||
self.init(width: width, height: height, feedNameRect: feedNameRect, dateRect: dateRect, titleRect: titleRect, numberOfLinesForTitle: numberOfLinesForTitle, summaryRect: summaryRect, textRect: textRect, unreadIndicatorRect: unreadIndicatorRect, starRect: starRect, avatarImageRect: avatarImageRect, separatorRect: separatorRect, paddingBottom: paddingBottom)
|
||||
self.init(width: width, height: height, feedNameRect: feedNameRect, dateRect: dateRect, titleRect: titleRect, numberOfLinesForTitle: numberOfLinesForTitle, summaryRect: summaryRect, textRect: textRect, unreadIndicatorRect: unreadIndicatorRect, starRect: starRect, iconImageRect: iconImageRect, separatorRect: separatorRect, paddingBottom: paddingBottom)
|
||||
}
|
||||
|
||||
static func height(for width: CGFloat, cellData: TimelineCellData, appearance: TimelineCellAppearance) -> CGFloat {
|
||||
|
||||
let layout = TimelineCellLayout(width: width, height: 0.0, cellData: cellData, appearance: appearance, hasAvatar: true)
|
||||
let layout = TimelineCellLayout(width: width, height: 0.0, cellData: cellData, appearance: appearance, hasIcon: true)
|
||||
return layout.height
|
||||
}
|
||||
}
|
||||
@ -93,12 +93,12 @@ struct TimelineCellLayout {
|
||||
|
||||
private extension TimelineCellLayout {
|
||||
|
||||
static func rectForTextBox(_ appearance: TimelineCellAppearance, _ cellData: TimelineCellData, _ showAvatar: Bool, _ width: CGFloat) -> NSRect {
|
||||
static func rectForTextBox(_ appearance: TimelineCellAppearance, _ cellData: TimelineCellData, _ showIcon: Bool, _ width: CGFloat) -> NSRect {
|
||||
|
||||
// Returned height is a placeholder. Not needed when this is calculated.
|
||||
|
||||
let avatarSpace = showAvatar ? appearance.avatarSize.width + appearance.avatarMarginRight : 0.0
|
||||
let textBoxOriginX = appearance.cellPadding.left + appearance.unreadCircleDimension + appearance.unreadCircleMarginRight + avatarSpace
|
||||
let iconSpace = showIcon ? appearance.iconSize.width + appearance.iconMarginRight : 0.0
|
||||
let textBoxOriginX = appearance.cellPadding.left + appearance.unreadCircleDimension + appearance.unreadCircleMarginRight + iconSpace
|
||||
let textBoxMaxX = floor(width - appearance.cellPadding.right)
|
||||
let textBoxWidth = floor(textBoxMaxX - textBoxOriginX)
|
||||
let textBoxRect = NSRect(x: textBoxOriginX, y: appearance.cellPadding.top, width: textBoxWidth, height: 1000000)
|
||||
@ -201,15 +201,15 @@ private extension TimelineCellLayout {
|
||||
return r
|
||||
}
|
||||
|
||||
static func rectForAvatar(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ showAvatar: Bool, _ textBoxRect: NSRect, _ width: CGFloat, _ height: CGFloat) -> NSRect {
|
||||
static func rectForIcon(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ showIcon: Bool, _ textBoxRect: NSRect, _ width: CGFloat, _ height: CGFloat) -> NSRect {
|
||||
|
||||
var r = NSRect.zero
|
||||
if !showAvatar {
|
||||
if !showIcon {
|
||||
return r
|
||||
}
|
||||
r.size = appearance.avatarSize
|
||||
r.size = appearance.iconSize
|
||||
r.origin.x = appearance.cellPadding.left + appearance.unreadCircleDimension + appearance.unreadCircleMarginRight
|
||||
r.origin.y = textBoxRect.origin.y + appearance.avatarAdjustmentTop
|
||||
r.origin.y = textBoxRect.origin.y + appearance.iconAdjustmentTop
|
||||
|
||||
return r
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// TimelineAvatarView.swift
|
||||
// TimelineIconView.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 9/15/19.
|
||||
@ -8,12 +8,12 @@
|
||||
|
||||
import AppKit
|
||||
|
||||
final class TimelineAvatarView: NSView {
|
||||
final class TimelineIconView: NSView {
|
||||
|
||||
var image: NSImage? = nil {
|
||||
var iconImage: IconImage? = nil {
|
||||
didSet {
|
||||
if image !== oldValue {
|
||||
imageView.image = image
|
||||
if iconImage !== oldValue {
|
||||
imageView.image = iconImage?.image
|
||||
needsDisplay = true
|
||||
needsLayout = true
|
||||
}
|
||||
@ -36,8 +36,8 @@ final class TimelineAvatarView: NSView {
|
||||
return imageView.frame.size.height < bounds.size.height
|
||||
}
|
||||
|
||||
private static var lightBackgroundColor = AppAssets.avatarLightBackgroundColor
|
||||
private static var darkBackgroundColor = AppAssets.avatarDarkBackgroundColor
|
||||
private static var lightBackgroundColor = AppAssets.iconLightBackgroundColor
|
||||
private static var darkBackgroundColor = AppAssets.iconDarkBackgroundColor
|
||||
|
||||
override init(frame frameRect: NSRect) {
|
||||
super.init(frame: frameRect)
|
||||
@ -71,13 +71,13 @@ final class TimelineAvatarView: NSView {
|
||||
return
|
||||
}
|
||||
|
||||
let color = NSApplication.shared.effectiveAppearance.isDarkMode ? TimelineAvatarView.darkBackgroundColor : TimelineAvatarView.lightBackgroundColor
|
||||
let color = NSApplication.shared.effectiveAppearance.isDarkMode ? TimelineIconView.darkBackgroundColor : TimelineIconView.lightBackgroundColor
|
||||
color.set()
|
||||
dirtyRect.fill()
|
||||
}
|
||||
}
|
||||
|
||||
private extension TimelineAvatarView {
|
||||
private extension TimelineIconView {
|
||||
|
||||
func commonInit() {
|
||||
addSubview(imageView)
|
||||
@ -85,7 +85,7 @@ private extension TimelineAvatarView {
|
||||
}
|
||||
|
||||
func rectForImageView() -> NSRect {
|
||||
guard let image = image else {
|
||||
guard let image = iconImage?.image else {
|
||||
return NSRect.zero
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ class TimelineTableCellView: NSTableCellView {
|
||||
private let dateView = TimelineTableCellView.singleLineTextField()
|
||||
private let feedNameView = TimelineTableCellView.singleLineTextField()
|
||||
|
||||
private lazy var avatarView = TimelineAvatarView()
|
||||
private lazy var iconView = TimelineIconView()
|
||||
|
||||
private let starView = TimelineTableCellView.imageView(with: AppAssets.timelineStar, scaling: .scaleNone)
|
||||
private let separatorView = TimelineTableCellView.separatorView()
|
||||
@ -37,7 +37,7 @@ class TimelineTableCellView: NSTableCellView {
|
||||
didSet {
|
||||
if cellAppearance != oldValue {
|
||||
updateTextFieldFonts()
|
||||
avatarView.layer?.cornerRadius = cellAppearance.avatarCornerRadius
|
||||
iconView.layer?.cornerRadius = cellAppearance.iconCornerRadius
|
||||
needsLayout = true
|
||||
}
|
||||
}
|
||||
@ -119,7 +119,7 @@ class TimelineTableCellView: NSTableCellView {
|
||||
dateView.rs_setFrameIfNotEqual(layoutRects.dateRect)
|
||||
unreadIndicatorView.rs_setFrameIfNotEqual(layoutRects.unreadIndicatorRect)
|
||||
feedNameView.rs_setFrameIfNotEqual(layoutRects.feedNameRect)
|
||||
avatarView.rs_setFrameIfNotEqual(layoutRects.avatarImageRect)
|
||||
iconView.rs_setFrameIfNotEqual(layoutRects.iconImageRect)
|
||||
starView.rs_setFrameIfNotEqual(layoutRects.starRect)
|
||||
separatorView.rs_setFrameIfNotEqual(layoutRects.separatorRect)
|
||||
}
|
||||
@ -207,7 +207,7 @@ private extension TimelineTableCellView {
|
||||
addSubviewAtInit(unreadIndicatorView, hidden: true)
|
||||
addSubviewAtInit(dateView, hidden: false)
|
||||
addSubviewAtInit(feedNameView, hidden: true)
|
||||
addSubviewAtInit(avatarView, hidden: true)
|
||||
addSubviewAtInit(iconView, hidden: true)
|
||||
addSubviewAtInit(starView, hidden: true)
|
||||
addSubviewAtInit(separatorView, hidden: !AppDefaults.timelineShowsSeparators)
|
||||
|
||||
@ -216,7 +216,7 @@ private extension TimelineTableCellView {
|
||||
|
||||
func updatedLayoutRects() -> TimelineCellLayout {
|
||||
|
||||
return TimelineCellLayout(width: bounds.width, height: bounds.height, cellData: cellData, appearance: cellAppearance, hasAvatar: avatarView.image != nil)
|
||||
return TimelineCellLayout(width: bounds.width, height: bounds.height, cellData: cellData, appearance: cellAppearance, hasIcon: iconView.iconImage != nil)
|
||||
}
|
||||
|
||||
func updateTitleView() {
|
||||
@ -265,25 +265,25 @@ private extension TimelineTableCellView {
|
||||
showOrHideView(starView, !cellData.starred)
|
||||
}
|
||||
|
||||
func updateAvatar() {
|
||||
guard let image = cellData.avatar, cellData.showAvatar else {
|
||||
makeAvatarEmpty()
|
||||
func updateIcon() {
|
||||
guard let iconImage = cellData.iconImage, cellData.showIcon else {
|
||||
makeIconEmpty()
|
||||
return
|
||||
}
|
||||
|
||||
showView(avatarView)
|
||||
if avatarView.image !== image {
|
||||
avatarView.image = image
|
||||
showView(iconView)
|
||||
if iconView.iconImage !== iconImage {
|
||||
iconView.iconImage = iconImage
|
||||
needsLayout = true
|
||||
}
|
||||
}
|
||||
|
||||
func makeAvatarEmpty() {
|
||||
if avatarView.image != nil {
|
||||
avatarView.image = nil
|
||||
func makeIconEmpty() {
|
||||
if iconView.iconImage != nil {
|
||||
iconView.iconImage = nil
|
||||
needsLayout = true
|
||||
}
|
||||
hideView(avatarView)
|
||||
hideView(iconView)
|
||||
}
|
||||
|
||||
func hideView(_ view: NSView) {
|
||||
@ -310,7 +310,7 @@ private extension TimelineTableCellView {
|
||||
updateFeedNameView()
|
||||
updateUnreadIndicator()
|
||||
updateStarView()
|
||||
updateAvatar()
|
||||
updateIcon()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
reloadVisibleCells()
|
||||
return
|
||||
}
|
||||
updateShowAvatars()
|
||||
updateShowIcons()
|
||||
articleRowMap = [String: Int]()
|
||||
tableView.reloadData()
|
||||
}
|
||||
@ -98,18 +98,18 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
private let fetchRequestQueue = FetchRequestQueue()
|
||||
private var articleRowMap = [String: Int]() // articleID: rowIndex
|
||||
private var cellAppearance: TimelineCellAppearance!
|
||||
private var cellAppearanceWithAvatar: TimelineCellAppearance!
|
||||
private var cellAppearanceWithIcon: TimelineCellAppearance!
|
||||
private var showFeedNames = false {
|
||||
didSet {
|
||||
if showFeedNames != oldValue {
|
||||
updateShowAvatars()
|
||||
updateShowIcons()
|
||||
updateTableViewRowHeight()
|
||||
reloadVisibleCells()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var showAvatars = false
|
||||
private var showIcons = false
|
||||
private var rowHeightWithFeedName: CGFloat = 0.0
|
||||
private var rowHeightWithoutFeedName: CGFloat = 0.0
|
||||
|
||||
@ -156,8 +156,8 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
cellAppearance = TimelineCellAppearance(showAvatar: false, fontSize: fontSize)
|
||||
cellAppearanceWithAvatar = TimelineCellAppearance(showAvatar: true, fontSize: fontSize)
|
||||
cellAppearance = TimelineCellAppearance(showIcon: false, fontSize: fontSize)
|
||||
cellAppearanceWithIcon = TimelineCellAppearance(showIcon: true, fontSize: fontSize)
|
||||
|
||||
updateRowHeights()
|
||||
tableView.rowHeight = currentRowHeight
|
||||
@ -438,7 +438,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
}
|
||||
|
||||
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
|
||||
guard showAvatars, let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
|
||||
guard showIcons, let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
|
||||
return
|
||||
}
|
||||
let indexesToReload = tableView.indexesOfAvailableRowsPassingTest { (row) -> Bool in
|
||||
@ -453,7 +453,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
}
|
||||
|
||||
@objc func avatarDidBecomeAvailable(_ note: Notification) {
|
||||
guard showAvatars, let avatarURL = note.userInfo?[UserInfoKey.url] as? String else {
|
||||
guard showIcons, let avatarURL = note.userInfo?[UserInfoKey.url] as? String else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -474,7 +474,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
}
|
||||
|
||||
@objc func faviconDidBecomeAvailable(_ note: Notification) {
|
||||
if showAvatars {
|
||||
if showIcons {
|
||||
queueReloadAvailableCells()
|
||||
}
|
||||
}
|
||||
@ -570,7 +570,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, userDeleted: false, dateArrived: Date())
|
||||
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, feedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status)
|
||||
|
||||
let prototypeCellData = TimelineCellData(article: prototypeArticle, showFeedName: showingFeedNames, feedName: "Prototype Feed Name", avatar: nil, showAvatar: false, featuredImage: nil)
|
||||
let prototypeCellData = TimelineCellData(article: prototypeArticle, showFeedName: showingFeedNames, feedName: "Prototype Feed Name", iconImage: nil, showIcon: false, featuredImage: nil)
|
||||
let height = TimelineCellLayout.height(for: 100, cellData: prototypeCellData, appearance: cellAppearance)
|
||||
return height
|
||||
}
|
||||
@ -674,7 +674,7 @@ extension TimelineViewController: NSTableViewDelegate {
|
||||
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
||||
|
||||
func configure(_ cell: TimelineTableCellView) {
|
||||
cell.cellAppearance = showAvatars ? cellAppearanceWithAvatar : cellAppearance
|
||||
cell.cellAppearance = showIcons ? cellAppearanceWithIcon : cellAppearance
|
||||
if let article = articles.articleAtRow(row) {
|
||||
configureTimelineCell(cell, article: article)
|
||||
}
|
||||
@ -716,12 +716,12 @@ extension TimelineViewController: NSTableViewDelegate {
|
||||
|
||||
private func configureTimelineCell(_ cell: TimelineTableCellView, article: Article) {
|
||||
cell.objectValue = article
|
||||
let avatar = article.avatarImage()
|
||||
cell.cellData = TimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, avatar: avatar, showAvatar: showAvatars, featuredImage: nil)
|
||||
let iconImage = article.iconImage()
|
||||
cell.cellData = TimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, iconImage: iconImage, showIcon: showIcons, featuredImage: nil)
|
||||
}
|
||||
|
||||
private func avatarFor(_ article: Article) -> NSImage? {
|
||||
if !showAvatars {
|
||||
private func iconFor(_ article: Article) -> IconImage? {
|
||||
if !showIcons {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -741,14 +741,14 @@ extension TimelineViewController: NSTableViewDelegate {
|
||||
return feedIcon
|
||||
}
|
||||
|
||||
if let favicon = appDelegate.faviconDownloader.faviconAsAvatar(for: feed) {
|
||||
if let favicon = appDelegate.faviconDownloader.faviconAsIcon(for: feed) {
|
||||
return favicon
|
||||
}
|
||||
|
||||
return FaviconGenerator.favicon(feed)
|
||||
}
|
||||
|
||||
private func avatarForAuthor(_ author: Author) -> NSImage? {
|
||||
private func avatarForAuthor(_ author: Author) -> IconImage? {
|
||||
return appDelegate.authorAvatarDownloader.image(for: author)
|
||||
}
|
||||
|
||||
@ -842,9 +842,9 @@ private extension TimelineViewController {
|
||||
tableView.rowHeight = currentRowHeight
|
||||
}
|
||||
|
||||
func updateShowAvatars() {
|
||||
func updateShowIcons() {
|
||||
if showFeedNames {
|
||||
self.showAvatars = true
|
||||
self.showIcons = true
|
||||
return
|
||||
}
|
||||
|
||||
@ -852,14 +852,14 @@ private extension TimelineViewController {
|
||||
if let authors = article.authors {
|
||||
for author in authors {
|
||||
if author.avatarURL != nil {
|
||||
self.showAvatars = true
|
||||
self.showIcons = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.showAvatars = false
|
||||
self.showIcons = false
|
||||
}
|
||||
|
||||
func emptyTheTimeline() {
|
||||
@ -935,8 +935,8 @@ private extension TimelineViewController {
|
||||
// MARK: - Appearance Change
|
||||
|
||||
private func fontSizeDidChange() {
|
||||
cellAppearance = TimelineCellAppearance(showAvatar: false, fontSize: fontSize)
|
||||
cellAppearanceWithAvatar = TimelineCellAppearance(showAvatar: true, fontSize: fontSize)
|
||||
cellAppearance = TimelineCellAppearance(showIcon: false, fontSize: fontSize)
|
||||
cellAppearanceWithIcon = TimelineCellAppearance(showIcon: true, fontSize: fontSize)
|
||||
updateRowHeights()
|
||||
performBlockAndRestoreSelection {
|
||||
tableView.reloadData()
|
||||
|
@ -89,6 +89,8 @@
|
||||
516A09402361240900EAE89B /* Account.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 516A093F2361240900EAE89B /* Account.storyboard */; };
|
||||
516A09422361248000EAE89B /* Inspector.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 516A09412361248000EAE89B /* Inspector.storyboard */; };
|
||||
516AE9B32371C372007DEEAA /* MasterFeedTableViewSectionHeaderLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9B22371C372007DEEAA /* MasterFeedTableViewSectionHeaderLayout.swift */; };
|
||||
516AE9DF2372269A007DEEAA /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; };
|
||||
516AE9E02372269A007DEEAA /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; };
|
||||
51707439232AA97100A461A3 /* ShareFolderPickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */; };
|
||||
5170743A232AABFC00A461A3 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */; };
|
||||
517630042336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; };
|
||||
@ -120,7 +122,7 @@
|
||||
51A1699F235E10D700EB091F /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16995235E10D600EB091F /* AboutViewController.swift */; };
|
||||
51A169A0235E10D700EB091F /* FeedbinAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */; };
|
||||
51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51AF460D232488C6001742EF /* Account-Extensions.swift */; };
|
||||
51B62E68233186730085F949 /* AvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B62E67233186730085F949 /* AvatarView.swift */; };
|
||||
51B62E68233186730085F949 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B62E67233186730085F949 /* IconView.swift */; };
|
||||
51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */; };
|
||||
51BB7C312335ACDE008E8144 /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = 51BB7C302335ACDE008E8144 /* page.html */; };
|
||||
51C451A9226377C200C03939 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; };
|
||||
@ -232,7 +234,6 @@
|
||||
51FA73AA2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */; };
|
||||
51FA73AB2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */; };
|
||||
51FA73B72332D5F70090D516 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */; };
|
||||
51FD40C72341555A00880194 /* UIImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FD40BD2341555600880194 /* UIImage-Extensions.swift */; };
|
||||
51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FD413A2342BD0500880194 /* MasterTimelineUnreadCountView.swift */; };
|
||||
51FE10032345529D0056195D /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; };
|
||||
51FE10042345529D0056195D /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; };
|
||||
@ -249,7 +250,7 @@
|
||||
6581C74220CED60100F4AD34 /* ToolbarItemIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */; };
|
||||
65ED3FB7235DEF6C0081F399 /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; };
|
||||
65ED3FB8235DEF6C0081F399 /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848B937121C8C5540038DC0D /* CrashReporter.swift */; };
|
||||
65ED3FB9235DEF6C0081F399 /* TimelineAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* TimelineAvatarView.swift */; };
|
||||
65ED3FB9235DEF6C0081F399 /* TimelineIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */; };
|
||||
65ED3FBA235DEF6C0081F399 /* ArticleExtractorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */; };
|
||||
65ED3FBB235DEF6C0081F399 /* InspectorWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BBB12C20142A4700F054F5 /* InspectorWindowController.swift */; };
|
||||
65ED3FBC235DEF6C0081F399 /* ColorHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F78227716380050506E /* ColorHash.swift */; };
|
||||
@ -486,7 +487,7 @@
|
||||
84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; };
|
||||
8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; };
|
||||
8477ACBE22238E9500DF7F37 /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; };
|
||||
847CD6CA232F4CBF00FAC46D /* TimelineAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* TimelineAvatarView.swift */; };
|
||||
847CD6CA232F4CBF00FAC46D /* TimelineIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */; };
|
||||
847E64A02262783000E00365 /* NSAppleEventDescriptor+UserRecordFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */; };
|
||||
848362FD2262A30800DA1D35 /* styleSheet.css in Resources */ = {isa = PBXBuildFile; fileRef = 848362FC2262A30800DA1D35 /* styleSheet.css */; };
|
||||
848362FF2262A30E00DA1D35 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; };
|
||||
@ -1253,6 +1254,7 @@
|
||||
516A093F2361240900EAE89B /* Account.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Account.storyboard; sourceTree = "<group>"; };
|
||||
516A09412361248000EAE89B /* Inspector.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Inspector.storyboard; sourceTree = "<group>"; };
|
||||
516AE9B22371C372007DEEAA /* MasterFeedTableViewSectionHeaderLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterFeedTableViewSectionHeaderLayout.swift; sourceTree = "<group>"; };
|
||||
516AE9DE2372269A007DEEAA /* IconImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconImage.swift; sourceTree = "<group>"; };
|
||||
51707438232AA97100A461A3 /* ShareFolderPickerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareFolderPickerController.swift; sourceTree = "<group>"; };
|
||||
517630032336215100E15FFF /* main.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = main.js; sourceTree = "<group>"; };
|
||||
517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleViewControllerWebViewProvider.swift; sourceTree = "<group>"; };
|
||||
@ -1280,7 +1282,7 @@
|
||||
51A16995235E10D600EB091F /* AboutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = "<group>"; };
|
||||
51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbinAccountViewController.swift; sourceTree = "<group>"; };
|
||||
51AF460D232488C6001742EF /* Account-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account-Extensions.swift"; sourceTree = "<group>"; };
|
||||
51B62E67233186730085F949 /* AvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarView.swift; sourceTree = "<group>"; };
|
||||
51B62E67233186730085F949 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = "<group>"; };
|
||||
51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = "<group>"; };
|
||||
51BB7C302335ACDE008E8144 /* page.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = page.html; sourceTree = "<group>"; };
|
||||
51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard-Extensions.swift"; sourceTree = "<group>"; };
|
||||
@ -1336,7 +1338,6 @@
|
||||
51FA73A62332BE880090D516 /* ExtractedArticle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtractedArticle.swift; sourceTree = "<group>"; };
|
||||
51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorConfig.swift; sourceTree = "<group>"; };
|
||||
51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = "<group>"; };
|
||||
51FD40BD2341555600880194 /* UIImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage-Extensions.swift"; sourceTree = "<group>"; };
|
||||
51FD413A2342BD0500880194 /* MasterTimelineUnreadCountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineUnreadCountView.swift; sourceTree = "<group>"; };
|
||||
51FE10022345529D0056195D /* UserNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationManager.swift; sourceTree = "<group>"; };
|
||||
51FFF0C3235EE8E5002762AA /* VibrantButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrantButton.swift; sourceTree = "<group>"; };
|
||||
@ -1402,7 +1403,7 @@
|
||||
8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = "<group>"; };
|
||||
847752FE2008879500D93690 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; };
|
||||
8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchFeedDelegate.swift; sourceTree = "<group>"; };
|
||||
847CD6C9232F4CBF00FAC46D /* TimelineAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineAvatarView.swift; sourceTree = "<group>"; };
|
||||
847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineIconView.swift; sourceTree = "<group>"; };
|
||||
847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAppleEventDescriptor+UserRecordFields.swift"; sourceTree = "<group>"; };
|
||||
848362FC2262A30800DA1D35 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; sourceTree = "<group>"; };
|
||||
848362FE2262A30E00DA1D35 /* template.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = template.html; sourceTree = "<group>"; };
|
||||
@ -1845,7 +1846,6 @@
|
||||
51934CC1230F5963006127BE /* ThemedNavigationController.swift */,
|
||||
51F85BF82274AA7B00C787DC /* UIBarButtonItem-Extensions.swift */,
|
||||
51F85BF622749FA100C787DC /* UIFont-Extensions.swift */,
|
||||
51FD40BD2341555600880194 /* UIImage-Extensions.swift */,
|
||||
512E092B2268B25500BDCFDD /* UISplitViewController-Extensions.swift */,
|
||||
51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */,
|
||||
51FFF0C3235EE8E5002762AA /* VibrantButton.swift */,
|
||||
@ -2246,7 +2246,7 @@
|
||||
84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */,
|
||||
849A97711ED9EC04007D329B /* TimelineCellData.swift */,
|
||||
849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */,
|
||||
847CD6C9232F4CBF00FAC46D /* TimelineAvatarView.swift */,
|
||||
847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */,
|
||||
);
|
||||
path = Cell;
|
||||
sourceTree = "<group>";
|
||||
@ -2283,6 +2283,7 @@
|
||||
849A97971ED9EFAA007D329B /* Node-Extensions.swift */,
|
||||
8405DD9B22153BD7008CE1BF /* NSView-Extensions.swift */,
|
||||
51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */,
|
||||
516AE9DE2372269A007DEEAA /* IconImage.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@ -2537,7 +2538,7 @@
|
||||
51C45255226507D200C03939 /* AppDefaults.swift */,
|
||||
51E3EB3C229AB08300645299 /* ErrorHandler.swift */,
|
||||
51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */,
|
||||
51B62E67233186730085F949 /* AvatarView.swift */,
|
||||
51B62E67233186730085F949 /* IconView.swift */,
|
||||
51C4525D226508F600C03939 /* MasterFeed */,
|
||||
51C4526D2265091600C03939 /* MasterTimeline */,
|
||||
51C4527D2265092C00C03939 /* Article */,
|
||||
@ -3723,7 +3724,7 @@
|
||||
files = (
|
||||
65ED3FB7235DEF6C0081F399 /* ArticleArray.swift in Sources */,
|
||||
65ED3FB8235DEF6C0081F399 /* CrashReporter.swift in Sources */,
|
||||
65ED3FB9235DEF6C0081F399 /* TimelineAvatarView.swift in Sources */,
|
||||
65ED3FB9235DEF6C0081F399 /* TimelineIconView.swift in Sources */,
|
||||
65ED3FBA235DEF6C0081F399 /* ArticleExtractorConfig.swift in Sources */,
|
||||
65ED3FBB235DEF6C0081F399 /* InspectorWindowController.swift in Sources */,
|
||||
65ED3FBC235DEF6C0081F399 /* ColorHash.swift in Sources */,
|
||||
@ -3886,7 +3887,7 @@
|
||||
5186A635235EF3A800C97195 /* VibrantLabel.swift in Sources */,
|
||||
51F85BF92274AA7B00C787DC /* UIBarButtonItem-Extensions.swift in Sources */,
|
||||
512AF9DF236F074B0066F8BE /* FeedInspectorLabelView.swift in Sources */,
|
||||
51B62E68233186730085F949 /* AvatarView.swift in Sources */,
|
||||
51B62E68233186730085F949 /* IconView.swift in Sources */,
|
||||
51C45296226509D300C03939 /* OPMLExporter.swift in Sources */,
|
||||
51C45291226509C800C03939 /* SmartFeed.swift in Sources */,
|
||||
51C452A722650A3D00C03939 /* RSImage-Extensions.swift in Sources */,
|
||||
@ -3914,7 +3915,6 @@
|
||||
51C452852265093600C03939 /* FlattenedAccountFolderPickerData.swift in Sources */,
|
||||
51C4526B226508F600C03939 /* MasterFeedViewController.swift in Sources */,
|
||||
5126EE97226CB48A00C22AFC /* SceneCoordinator.swift in Sources */,
|
||||
51FD40C72341555A00880194 /* UIImage-Extensions.swift in Sources */,
|
||||
84CAFCB022BC8C35007694F0 /* FetchRequestOperation.swift in Sources */,
|
||||
51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */,
|
||||
51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */,
|
||||
@ -3977,6 +3977,7 @@
|
||||
51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */,
|
||||
84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */,
|
||||
512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */,
|
||||
516AE9E02372269A007DEEAA /* IconImage.swift in Sources */,
|
||||
519D740623243CC0008BB345 /* RefreshInterval-Extensions.swift in Sources */,
|
||||
51C45268226508F600C03939 /* MasterFeedUnreadCountView.swift in Sources */,
|
||||
5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */,
|
||||
@ -4006,7 +4007,7 @@
|
||||
files = (
|
||||
84F204E01FAACBB30076E152 /* ArticleArray.swift in Sources */,
|
||||
848B937221C8C5540038DC0D /* CrashReporter.swift in Sources */,
|
||||
847CD6CA232F4CBF00FAC46D /* TimelineAvatarView.swift in Sources */,
|
||||
847CD6CA232F4CBF00FAC46D /* TimelineIconView.swift in Sources */,
|
||||
51FA73AA2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */,
|
||||
84BBB12E20142A4700F054F5 /* InspectorWindowController.swift in Sources */,
|
||||
51EF0F7A22771B890050506E /* ColorHash.swift in Sources */,
|
||||
@ -4022,6 +4023,7 @@
|
||||
84F2D53A1FC2308B00998D64 /* UnreadFeed.swift in Sources */,
|
||||
513228FB233037630033D4ED /* Reachability.swift in Sources */,
|
||||
845A29221FC9251E007B49E3 /* SidebarCellLayout.swift in Sources */,
|
||||
516AE9DF2372269A007DEEAA /* IconImage.swift in Sources */,
|
||||
84AD1EBA2031649C00BC20B7 /* SmartFeedPasteboardWriter.swift in Sources */,
|
||||
849C78922362AB04009A71E4 /* ExportOPMLWindowController.swift in Sources */,
|
||||
84CC88181FE59CBF00644329 /* SmartFeedsController.swift in Sources */,
|
||||
|
@ -226,8 +226,8 @@ private extension ActivityManager {
|
||||
attributeSet.keywords = makeKeywords(article)
|
||||
attributeSet.relatedUniqueIdentifier = ActivityManager.identifer(for: article)
|
||||
|
||||
if let image = article.avatarImage() {
|
||||
attributeSet.thumbnailData = image.pngData()
|
||||
if let iconImage = article.iconImage() {
|
||||
attributeSet.thumbnailData = iconImage.image.pngData()
|
||||
}
|
||||
|
||||
readingActivity?.contentAttributeSet = attributeSet
|
||||
@ -252,18 +252,10 @@ private extension ActivityManager {
|
||||
attributeSet.title = feed.nameForDisplay
|
||||
attributeSet.keywords = makeKeywords(feed.nameForDisplay)
|
||||
attributeSet.relatedUniqueIdentifier = ActivityManager.identifer(for: feed)
|
||||
if let image = appDelegate.feedIconDownloader.icon(for: feed) {
|
||||
#if os(iOS)
|
||||
attributeSet.thumbnailData = image.pngData()
|
||||
#else
|
||||
attributeSet.thumbnailData = image.tiffRepresentation
|
||||
#endif
|
||||
} else if let image = appDelegate.faviconDownloader.faviconAsAvatar(for: feed) {
|
||||
#if os(iOS)
|
||||
attributeSet.thumbnailData = image.pngData()
|
||||
#else
|
||||
attributeSet.thumbnailData = image.tiffRepresentation
|
||||
#endif
|
||||
if let iconImage = appDelegate.feedIconDownloader.icon(for: feed) {
|
||||
attributeSet.thumbnailData = iconImage.image.dataRepresentation()
|
||||
} else if let iconImage = appDelegate.faviconDownloader.faviconAsIcon(for: feed) {
|
||||
attributeSet.thumbnailData = iconImage.image.dataRepresentation()
|
||||
}
|
||||
|
||||
selectingActivity!.contentAttributeSet = attributeSet
|
||||
|
@ -202,9 +202,9 @@ private extension ArticleRenderer {
|
||||
return cachedImgTag
|
||||
}
|
||||
|
||||
if let favicon = appDelegate.faviconDownloader.faviconAsAvatar(for: feed) {
|
||||
if let s = base64String(forImage: favicon) {
|
||||
var dimension = min(favicon.size.height, CGFloat(ArticleRenderer.avatarDimension)) // Assuming square images.
|
||||
if let iconImage = appDelegate.faviconDownloader.faviconAsIcon(for: feed) {
|
||||
if let s = base64String(forImage: iconImage.image) {
|
||||
var dimension = min(iconImage.image.size.height, CGFloat(ArticleRenderer.avatarDimension)) // Assuming square images.
|
||||
dimension = max(dimension, 16) // Some favicons say they’re < 16. Force them larger.
|
||||
if dimension >= CGFloat(ArticleRenderer.avatarDimension) * 0.8 { //Close enough to scale up.
|
||||
dimension = CGFloat(ArticleRenderer.avatarDimension)
|
||||
@ -231,8 +231,8 @@ private extension ArticleRenderer {
|
||||
return cachedImgTag
|
||||
}
|
||||
|
||||
if let icon = appDelegate.feedIconDownloader.icon(for: feed) {
|
||||
if let s = base64String(forImage: icon) {
|
||||
if let iconImage = appDelegate.feedIconDownloader.icon(for: feed) {
|
||||
if let s = base64String(forImage: iconImage.image) {
|
||||
#if os(macOS)
|
||||
let imgTag = "<img src=\"data:image/tiff;base64, " + s + "\" height=48 width=48 />"
|
||||
#else
|
||||
|
@ -64,7 +64,7 @@ extension Article {
|
||||
return datePublished ?? dateModified ?? status.dateArrived
|
||||
}
|
||||
|
||||
func avatarImage() -> RSImage? {
|
||||
func iconImage() -> IconImage? {
|
||||
if let authors = authors {
|
||||
for author in authors {
|
||||
if let image = appDelegate.authorAvatarDownloader.image(for: author) {
|
||||
@ -82,7 +82,7 @@ extension Article {
|
||||
return feedIconImage
|
||||
}
|
||||
|
||||
if let faviconImage = appDelegate.faviconDownloader.faviconAsAvatar(for: feed) {
|
||||
if let faviconImage = appDelegate.faviconDownloader.faviconAsIcon(for: feed) {
|
||||
return faviconImage
|
||||
}
|
||||
|
||||
|
@ -13,14 +13,14 @@ import RSCore
|
||||
|
||||
protocol SmallIconProvider {
|
||||
|
||||
var smallIcon: RSImage? { get }
|
||||
var smallIcon: IconImage? { get }
|
||||
}
|
||||
|
||||
extension Feed: SmallIconProvider {
|
||||
|
||||
var smallIcon: RSImage? {
|
||||
if let image = appDelegate.faviconDownloader.favicon(for: self) {
|
||||
return image
|
||||
var smallIcon: IconImage? {
|
||||
if let iconImage = appDelegate.faviconDownloader.favicon(for: self) {
|
||||
return iconImage
|
||||
}
|
||||
#if os(macOS)
|
||||
return AppAssets.genericFeedImage
|
||||
@ -31,13 +31,7 @@ extension Feed: SmallIconProvider {
|
||||
}
|
||||
|
||||
extension Folder: SmallIconProvider {
|
||||
|
||||
var smallIcon: RSImage? {
|
||||
#if os(macOS)
|
||||
return RSImage(named: NSImage.folderName)
|
||||
#else
|
||||
return AppAssets.masterFolderImage
|
||||
#endif
|
||||
var smallIcon: IconImage? {
|
||||
AppAssets.masterFolderImage
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,12 +1,41 @@
|
||||
//
|
||||
// UIImage-Extensions.swift
|
||||
// IconImage.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Maurice Parker on 9/29/19.
|
||||
// Created by Maurice Parker on 11/5/19.
|
||||
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Foundation
|
||||
import RSCore
|
||||
|
||||
final class IconImage {
|
||||
|
||||
lazy var isDark: Bool = {
|
||||
return image.isDark()
|
||||
}()
|
||||
|
||||
let image: RSImage
|
||||
|
||||
init(_ image: RSImage) {
|
||||
self.image = image
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
extension NSImage {
|
||||
func isDark() -> Bool {
|
||||
return self.cgImage(forProposedRect: nil, context: nil, hints: nil)?.isDark() ?? false
|
||||
}
|
||||
}
|
||||
#else
|
||||
extension UIImage {
|
||||
func isDark() -> Bool {
|
||||
return self.cgImage?.isDark() ?? false
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
extension CGImage {
|
||||
|
||||
@ -35,9 +64,3 @@ extension CGImage {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension UIImage {
|
||||
func isDark() -> Bool {
|
||||
return self.cgImage?.isDark() ?? false
|
||||
}
|
||||
}
|
@ -11,19 +11,19 @@ import RSCore
|
||||
|
||||
extension RSImage {
|
||||
|
||||
static let avatarSize = 48
|
||||
static let maxIconSize = 48
|
||||
|
||||
static func scaledForAvatar(_ data: Data, imageResultBlock: @escaping (RSImage?) -> Void) {
|
||||
static func scaledForIcon(_ data: Data, imageResultBlock: @escaping (RSImage?) -> Void) {
|
||||
DispatchQueue.global(qos: .default).async {
|
||||
let image = RSImage.scaledForAvatar(data)
|
||||
let image = RSImage.scaledForIcon(data)
|
||||
DispatchQueue.main.async {
|
||||
imageResultBlock(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func scaledForAvatar(_ data: Data) -> RSImage? {
|
||||
let scaledMaxPixelSize = Int(ceil(CGFloat(RSImage.avatarSize) * RSScreen.mainScreenScale))
|
||||
static func scaledForIcon(_ data: Data) -> RSImage? {
|
||||
let scaledMaxPixelSize = Int(ceil(CGFloat(RSImage.maxIconSize) * RSScreen.mainScreenScale))
|
||||
guard var cgImage = RSImage.scaleImage(data, maxPixelSize: scaledMaxPixelSize) else {
|
||||
return nil
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ final class FaviconDownloader {
|
||||
}
|
||||
|
||||
private let queue: DispatchQueue
|
||||
private var cache = [Feed: RSImage]() // faviconURL: RSImage
|
||||
private var cache = [Feed: IconImage]() // faviconURL: RSImage
|
||||
|
||||
struct UserInfoKey {
|
||||
static let faviconURL = "faviconURL"
|
||||
@ -64,10 +64,10 @@ final class FaviconDownloader {
|
||||
// MARK: - API
|
||||
|
||||
func resetCache() {
|
||||
cache = [Feed: RSImage]()
|
||||
cache = [Feed: IconImage]()
|
||||
}
|
||||
|
||||
func favicon(for feed: Feed) -> RSImage? {
|
||||
func favicon(for feed: Feed) -> IconImage? {
|
||||
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
@ -89,29 +89,30 @@ final class FaviconDownloader {
|
||||
return nil
|
||||
}
|
||||
|
||||
func faviconAsAvatar(for feed: Feed) -> RSImage? {
|
||||
func faviconAsIcon(for feed: Feed) -> IconImage? {
|
||||
|
||||
if let image = cache[feed] {
|
||||
return image
|
||||
}
|
||||
|
||||
if let image = favicon(for: feed), let imageData = image.dataRepresentation() {
|
||||
if let scaledImage = RSImage.scaledForAvatar(imageData) {
|
||||
cache[feed] = scaledImage
|
||||
return scaledImage
|
||||
if let iconImage = favicon(for: feed), let imageData = iconImage.image.dataRepresentation() {
|
||||
if let scaledImage = RSImage.scaledForIcon(imageData) {
|
||||
let scaledIconImage = IconImage(scaledImage)
|
||||
cache[feed] = scaledIconImage
|
||||
return scaledIconImage
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func favicon(with faviconURL: String) -> RSImage? {
|
||||
func favicon(with faviconURL: String) -> IconImage? {
|
||||
|
||||
let downloader = faviconDownloader(withURL: faviconURL)
|
||||
return downloader.image
|
||||
return downloader.iconImage
|
||||
}
|
||||
|
||||
func favicon(withHomePageURL homePageURL: String) -> RSImage? {
|
||||
func favicon(withHomePageURL homePageURL: String) -> IconImage? {
|
||||
|
||||
let url = homePageURL.rs_normalizedURL()
|
||||
if homePageURLsWithNoFaviconURLCache.contains(url) {
|
||||
@ -144,7 +145,7 @@ final class FaviconDownloader {
|
||||
guard let singleFaviconDownloader = note.object as? SingleFaviconDownloader else {
|
||||
return
|
||||
}
|
||||
guard let _ = singleFaviconDownloader.image else {
|
||||
guard let _ = singleFaviconDownloader.iconImage else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -12,9 +12,9 @@ import Account
|
||||
|
||||
final class FaviconGenerator {
|
||||
|
||||
private static var faviconGeneratorCache = [String: RSImage]() // feedURL: RSImage
|
||||
private static var faviconGeneratorCache = [String: IconImage]() // feedURL: RSImage
|
||||
|
||||
static func favicon(_ feed: Feed) -> RSImage {
|
||||
static func favicon(_ feed: Feed) -> IconImage {
|
||||
|
||||
if let favicon = FaviconGenerator.faviconGeneratorCache[feed.url] {
|
||||
return favicon
|
||||
@ -22,10 +22,11 @@ final class FaviconGenerator {
|
||||
|
||||
let colorHash = ColorHash(feed.url)
|
||||
if let favicon = AppAssets.faviconTemplateImage.maskWithColor(color: colorHash.color.cgColor) {
|
||||
FaviconGenerator.faviconGeneratorCache[feed.url] = favicon
|
||||
return favicon
|
||||
let iconImage = IconImage(favicon)
|
||||
FaviconGenerator.faviconGeneratorCache[feed.url] = iconImage
|
||||
return iconImage
|
||||
} else {
|
||||
return AppAssets.faviconTemplateImage
|
||||
return IconImage(AppAssets.faviconTemplateImage)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ final class SingleFaviconDownloader {
|
||||
}
|
||||
|
||||
let faviconURL: String
|
||||
var image: RSImage?
|
||||
var iconImage: IconImage?
|
||||
|
||||
private var lastDownloadAttemptDate: Date
|
||||
private var diskStatus = DiskStatus.unknown
|
||||
@ -50,7 +50,7 @@ final class SingleFaviconDownloader {
|
||||
|
||||
// If we don’t have an image, and lastDownloadAttemptDate is a while ago, try again.
|
||||
|
||||
if let _ = image {
|
||||
if let _ = iconImage {
|
||||
return
|
||||
}
|
||||
|
||||
@ -72,7 +72,7 @@ private extension SingleFaviconDownloader {
|
||||
|
||||
if let image = image {
|
||||
self.diskStatus = .onDisk
|
||||
self.image = image
|
||||
self.iconImage = IconImage(image)
|
||||
self.postDidLoadFaviconNotification()
|
||||
return
|
||||
}
|
||||
@ -82,7 +82,7 @@ private extension SingleFaviconDownloader {
|
||||
self.downloadFavicon { (image) in
|
||||
|
||||
if let image = image {
|
||||
self.image = image
|
||||
self.iconImage = IconImage(image)
|
||||
self.postDidLoadFaviconNotification()
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ extension Notification.Name {
|
||||
final class AuthorAvatarDownloader {
|
||||
|
||||
private let imageDownloader: ImageDownloader
|
||||
private var cache = [String: RSImage]() // avatarURL: RSImage
|
||||
private var cache = [String: IconImage]() // avatarURL: RSImage
|
||||
private var waitingForAvatarURLs = Set<String>()
|
||||
|
||||
init(imageDownloader: ImageDownloader) {
|
||||
@ -28,10 +28,10 @@ final class AuthorAvatarDownloader {
|
||||
}
|
||||
|
||||
func resetCache() {
|
||||
cache = [String: RSImage]()
|
||||
cache = [String: IconImage]()
|
||||
}
|
||||
|
||||
func image(for author: Author) -> RSImage? {
|
||||
func image(for author: Author) -> IconImage? {
|
||||
|
||||
guard let avatarURL = author.avatarURL else {
|
||||
return nil
|
||||
@ -68,7 +68,7 @@ final class AuthorAvatarDownloader {
|
||||
private extension AuthorAvatarDownloader {
|
||||
|
||||
func scaleAndCacheImageData(_ imageData: Data, _ avatarURL: String) {
|
||||
RSImage.scaledForAvatar(imageData) { (image) in
|
||||
RSImage.scaledForIcon(imageData) { (image) in
|
||||
if let image = image {
|
||||
self.handleImageDidBecomeAvailable(avatarURL, image)
|
||||
}
|
||||
@ -77,7 +77,7 @@ private extension AuthorAvatarDownloader {
|
||||
|
||||
func handleImageDidBecomeAvailable(_ avatarURL: String, _ image: RSImage) {
|
||||
if cache[avatarURL] == nil {
|
||||
cache[avatarURL] = image
|
||||
cache[avatarURL] = IconImage(image)
|
||||
}
|
||||
if waitingForAvatarURLs.contains(avatarURL) {
|
||||
waitingForAvatarURLs.remove(avatarURL)
|
||||
|
@ -41,7 +41,7 @@ public final class FeedIconDownloader {
|
||||
}
|
||||
|
||||
private var urlsInProgress = Set<String>()
|
||||
private var cache = [Feed: RSImage]()
|
||||
private var cache = [Feed: IconImage]()
|
||||
private var waitingForFeedURLs = [String: Feed]()
|
||||
|
||||
init(imageDownloader: ImageDownloader, folder: String) {
|
||||
@ -54,10 +54,10 @@ public final class FeedIconDownloader {
|
||||
}
|
||||
|
||||
func resetCache() {
|
||||
cache = [Feed: RSImage]()
|
||||
cache = [Feed: IconImage]()
|
||||
}
|
||||
|
||||
func icon(for feed: Feed) -> RSImage? {
|
||||
func icon(for feed: Feed) -> IconImage? {
|
||||
|
||||
if let cachedImage = cache[feed] {
|
||||
return cachedImage
|
||||
@ -70,7 +70,7 @@ public final class FeedIconDownloader {
|
||||
icon(forHomePageURL: homePageURL, feed: feed) { (image) in
|
||||
if let image = image {
|
||||
self.postFeedIconDidBecomeAvailableNotification(feed)
|
||||
self.cache[feed] = image
|
||||
self.cache[feed] = IconImage(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -79,7 +79,7 @@ public final class FeedIconDownloader {
|
||||
icon(forURL: iconURL, feed: feed) { (image) in
|
||||
if let image = image {
|
||||
self.postFeedIconDidBecomeAvailableNotification(feed)
|
||||
self.cache[feed] = image
|
||||
self.cache[feed] = IconImage(image)
|
||||
}
|
||||
else {
|
||||
checkHomePageURL()
|
||||
@ -139,7 +139,7 @@ private extension FeedIconDownloader {
|
||||
imageResultBlock(nil)
|
||||
return
|
||||
}
|
||||
RSImage.scaledForAvatar(imageData, imageResultBlock: imageResultBlock)
|
||||
RSImage.scaledForIcon(imageData, imageResultBlock: imageResultBlock)
|
||||
}
|
||||
|
||||
func postFeedIconDidBecomeAvailableNotification(_ feed: Feed) {
|
||||
|
@ -20,7 +20,7 @@ struct SearchFeedDelegate: SmartFeedDelegate {
|
||||
let nameForDisplayPrefix = NSLocalizedString("Search: ", comment: "Search smart feed title prefix")
|
||||
let searchString: String
|
||||
let fetchType: FetchType
|
||||
var smallIcon: RSImage? = AppAssets.searchFeedImage
|
||||
var smallIcon: IconImage? = AppAssets.searchFeedImage
|
||||
|
||||
init(searchString: String) {
|
||||
self.searchString = searchString
|
||||
|
@ -20,7 +20,7 @@ struct SearchTimelineFeedDelegate: SmartFeedDelegate {
|
||||
let nameForDisplayPrefix = NSLocalizedString("Search: ", comment: "Search smart feed title prefix")
|
||||
let searchString: String
|
||||
let fetchType: FetchType
|
||||
var smallIcon: RSImage? = AppAssets.searchFeedImage
|
||||
var smallIcon: IconImage? = AppAssets.searchFeedImage
|
||||
|
||||
init(searchString: String, articleIDs: Set<String>) {
|
||||
self.searchString = searchString
|
||||
|
@ -25,7 +25,7 @@ final class SmartFeed: PseudoFeed {
|
||||
}
|
||||
}
|
||||
|
||||
var smallIcon: RSImage? {
|
||||
var smallIcon: IconImage? {
|
||||
return delegate.smallIcon
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ struct StarredFeedDelegate: SmartFeedDelegate {
|
||||
|
||||
let nameForDisplay = NSLocalizedString("Starred", comment: "Starred pseudo-feed title")
|
||||
let fetchType: FetchType = .starred
|
||||
var smallIcon: RSImage? = AppAssets.starredFeedImage
|
||||
var smallIcon: IconImage? = AppAssets.starredFeedImage
|
||||
|
||||
func fetchUnreadCount(for account: Account, callback: @escaping (Int) -> Void) {
|
||||
account.fetchUnreadCountForStarredArticles(callback)
|
||||
|
@ -15,7 +15,7 @@ struct TodayFeedDelegate: SmartFeedDelegate {
|
||||
|
||||
let nameForDisplay = NSLocalizedString("Today", comment: "Today pseudo-feed title")
|
||||
let fetchType = FetchType.today
|
||||
var smallIcon: RSImage? = AppAssets.todayFeedImage
|
||||
var smallIcon: IconImage? = AppAssets.todayFeedImage
|
||||
|
||||
func fetchUnreadCount(for account: Account, callback: @escaping (Int) -> Void) {
|
||||
account.fetchUnreadCountForToday(callback)
|
||||
|
@ -30,7 +30,7 @@ final class UnreadFeed: PseudoFeed {
|
||||
}
|
||||
}
|
||||
|
||||
var smallIcon: RSImage? = AppAssets.unreadFeedImage
|
||||
var smallIcon: IconImage? = AppAssets.unreadFeedImage
|
||||
|
||||
#if os(macOS)
|
||||
var pasteboardWriter: NSPasteboardWriting {
|
||||
|
@ -49,8 +49,8 @@ struct AppAssets {
|
||||
return image.maskWithColor(color: AppAssets.primaryAccentColor.cgColor)!
|
||||
}()
|
||||
|
||||
static var avatarBackgroundColor: UIColor = {
|
||||
return UIColor(named: "avatarBackgroundColor")!
|
||||
static var iconBackgroundColor: UIColor = {
|
||||
return UIColor(named: "iconBackgroundColor")!
|
||||
}()
|
||||
|
||||
static var barBackgroundColor: UIColor = {
|
||||
@ -105,8 +105,8 @@ struct AppAssets {
|
||||
return UIImage(systemName: "arrowtriangle.up.circle")!
|
||||
}()
|
||||
|
||||
static var masterFolderImage: UIImage = {
|
||||
return UIImage(systemName: "folder.fill")!
|
||||
static var masterFolderImage: IconImage = {
|
||||
return IconImage(UIImage(systemName: "folder.fill")!)
|
||||
}()
|
||||
|
||||
static var moreImage: UIImage = {
|
||||
@ -125,8 +125,8 @@ struct AppAssets {
|
||||
return UIImage(systemName: "safari")!
|
||||
}()
|
||||
|
||||
static var searchFeedImage: UIImage = {
|
||||
return UIImage(systemName: "magnifyingglass")!
|
||||
static var searchFeedImage: IconImage = {
|
||||
return IconImage(UIImage(systemName: "magnifyingglass")!)
|
||||
}()
|
||||
|
||||
static var secondaryAccentColor: UIColor = {
|
||||
@ -157,8 +157,8 @@ struct AppAssets {
|
||||
return UIImage(systemName: "star")!
|
||||
}()
|
||||
|
||||
static var starredFeedImage: UIImage = {
|
||||
return UIImage(systemName: "star.fill")!
|
||||
static var starredFeedImage: IconImage = {
|
||||
return IconImage(UIImage(systemName: "star.fill")!)
|
||||
}()
|
||||
|
||||
static var timelineStarImage: UIImage = {
|
||||
@ -166,16 +166,16 @@ struct AppAssets {
|
||||
return image.withTintColor(AppAssets.starColor, renderingMode: .alwaysOriginal)
|
||||
}()
|
||||
|
||||
static var todayFeedImage: UIImage = {
|
||||
return UIImage(systemName: "sun.max.fill")!
|
||||
static var todayFeedImage: IconImage = {
|
||||
return IconImage(UIImage(systemName: "sun.max.fill")!)
|
||||
}()
|
||||
|
||||
static var trashImage: UIImage = {
|
||||
return UIImage(systemName: "trash")!
|
||||
}()
|
||||
|
||||
static var unreadFeedImage: UIImage = {
|
||||
return UIImage(systemName: "largecircle.fill.circle")!
|
||||
static var unreadFeedImage: IconImage = {
|
||||
return IconImage(UIImage(systemName: "largecircle.fill.circle")!)
|
||||
}()
|
||||
|
||||
static var vibrantTextColor: UIColor = {
|
||||
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// AvatarView.swift
|
||||
// IconView.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Maurice Parker on 9/17/19.
|
||||
@ -8,16 +8,16 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
final class AvatarView: UIView {
|
||||
final class IconView: UIView {
|
||||
|
||||
var image: UIImage? = nil {
|
||||
var iconImage: IconImage? = nil {
|
||||
didSet {
|
||||
if image !== oldValue {
|
||||
imageView.image = image
|
||||
if iconImage !== oldValue {
|
||||
imageView.image = iconImage?.image
|
||||
|
||||
if self.traitCollection.userInterfaceStyle == .dark {
|
||||
DispatchQueue.global(qos: .default).async {
|
||||
if self.image?.isDark() ?? false {
|
||||
if self.iconImage?.isDark ?? false {
|
||||
DispatchQueue.main.async {
|
||||
self.isDisconcernable = false
|
||||
self.setNeedsLayout()
|
||||
@ -74,8 +74,8 @@ final class AvatarView: UIView {
|
||||
|
||||
override func layoutSubviews() {
|
||||
imageView.setFrameIfNotEqual(rectForImageView())
|
||||
if (image != nil && isVerticalBackgroundExposed && !isSymbolImage) || !isDisconcernable {
|
||||
backgroundColor = AppAssets.avatarBackgroundColor
|
||||
if (iconImage != nil && isVerticalBackgroundExposed && !isSymbolImage) || !isDisconcernable {
|
||||
backgroundColor = AppAssets.iconBackgroundColor
|
||||
} else {
|
||||
backgroundColor = nil
|
||||
}
|
||||
@ -83,16 +83,16 @@ final class AvatarView: UIView {
|
||||
|
||||
}
|
||||
|
||||
private extension AvatarView {
|
||||
private extension IconView {
|
||||
|
||||
func commonInit() {
|
||||
layer.cornerRadius = MasterTimelineDefaultCellLayout.avatarCornerRadius
|
||||
layer.cornerRadius = MasterTimelineDefaultCellLayout.iconCornerRadius
|
||||
clipsToBounds = true
|
||||
addSubview(imageView)
|
||||
}
|
||||
|
||||
func rectForImageView() -> CGRect {
|
||||
guard let image = image else {
|
||||
guard let image = iconImage?.image else {
|
||||
return CGRect.zero
|
||||
}
|
||||
|
@ -22,28 +22,28 @@ struct FeedInspectorView : View {
|
||||
Section(header:
|
||||
HStack {
|
||||
Spacer()
|
||||
if self.viewModel.image.size.width < 32 || self.viewModel.image.size.height < 32 {
|
||||
if colorScheme == .dark && self.viewModel.image.isDark() {
|
||||
Image(uiImage: self.viewModel.image)
|
||||
if self.viewModel.iconImage.image.size.width < 32 || self.viewModel.iconImage.image.size.height < 32 {
|
||||
if colorScheme == .dark && self.viewModel.iconImage.isDark {
|
||||
Image(uiImage: self.viewModel.iconImage.image)
|
||||
.resizable()
|
||||
.background(Color(AppAssets.avatarBackgroundColor))
|
||||
.background(Color(AppAssets.iconBackgroundColor))
|
||||
.frame(width: 24.0, height: 24.0)
|
||||
.cornerRadius(2.0)
|
||||
} else {
|
||||
Image(uiImage: self.viewModel.image)
|
||||
Image(uiImage: self.viewModel.iconImage.image)
|
||||
.resizable()
|
||||
.frame(width: 24.0, height: 24.0)
|
||||
.cornerRadius(2.0)
|
||||
}
|
||||
} else {
|
||||
if colorScheme == .dark && self.viewModel.image.isDark() {
|
||||
Image(uiImage: self.viewModel.image)
|
||||
if colorScheme == .dark && self.viewModel.iconImage.isDark {
|
||||
Image(uiImage: self.viewModel.iconImage.image)
|
||||
.resizable()
|
||||
.background(Color(AppAssets.avatarBackgroundColor))
|
||||
.background(Color(AppAssets.iconBackgroundColor))
|
||||
.frame(width: 48.0, height: 48.0)
|
||||
.cornerRadius(5.0)
|
||||
} else {
|
||||
Image(uiImage: self.viewModel.image)
|
||||
Image(uiImage: self.viewModel.iconImage.image)
|
||||
.resizable()
|
||||
.frame(width: 48.0, height: 48.0)
|
||||
.cornerRadius(5.0)
|
||||
@ -88,7 +88,7 @@ struct FeedInspectorView : View {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
|
||||
}
|
||||
|
||||
var image: UIImage {
|
||||
var iconImage: IconImage {
|
||||
if let feedIcon = appDelegate.feedIconDownloader.icon(for: feed) {
|
||||
return feedIcon
|
||||
}
|
||||
|
@ -31,9 +31,9 @@ class MasterFeedTableViewCell : VibrantTableViewCell {
|
||||
}
|
||||
}
|
||||
|
||||
var avatarImage: UIImage? {
|
||||
var iconImage: IconImage? {
|
||||
didSet {
|
||||
avatarView.image = avatarImage
|
||||
iconView.iconImage = iconImage
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ class MasterFeedTableViewCell : VibrantTableViewCell {
|
||||
return label
|
||||
}()
|
||||
|
||||
private let avatarView = AvatarView()
|
||||
private let iconView = IconView()
|
||||
|
||||
private let bottomSeparatorView: UIView = {
|
||||
let view = UIView()
|
||||
@ -154,9 +154,9 @@ class MasterFeedTableViewCell : VibrantTableViewCell {
|
||||
|
||||
override func updateVibrancy(animated: Bool) {
|
||||
super.updateVibrancy(animated: animated)
|
||||
let avatarTintColor = isHighlighted || isSelected ? AppAssets.vibrantTextColor : AppAssets.secondaryAccentColor
|
||||
let iconTintColor = isHighlighted || isSelected ? AppAssets.vibrantTextColor : AppAssets.secondaryAccentColor
|
||||
UIView.animate(withDuration: duration(animated: animated)) {
|
||||
self.avatarView.tintColor = avatarTintColor
|
||||
self.iconView.tintColor = iconTintColor
|
||||
}
|
||||
updateLabelVibrancy(titleView, color: labelColor, animated: animated)
|
||||
}
|
||||
@ -167,7 +167,7 @@ private extension MasterFeedTableViewCell {
|
||||
|
||||
func commonInit() {
|
||||
addSubviewAtInit(unreadCountView)
|
||||
addSubviewAtInit(avatarView)
|
||||
addSubviewAtInit(iconView)
|
||||
addSubviewAtInit(titleView)
|
||||
addDisclosureView()
|
||||
addSubviewAtInit(bottomSeparatorView)
|
||||
@ -189,7 +189,7 @@ private extension MasterFeedTableViewCell {
|
||||
}
|
||||
|
||||
func layoutWith(_ layout: MasterFeedTableViewCellLayout) {
|
||||
avatarView.setFrameIfNotEqual(layout.faviconRect)
|
||||
iconView.setFrameIfNotEqual(layout.faviconRect)
|
||||
titleView.setFrameIfNotEqual(layout.titleRect)
|
||||
unreadCountView.setFrameIfNotEqual(layout.unreadCountRect)
|
||||
disclosureButton?.setFrameIfNotEqual(layout.disclosureButtonRect)
|
||||
|
@ -105,11 +105,11 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||
}
|
||||
|
||||
@objc func faviconDidBecomeAvailable(_ note: Notification) {
|
||||
applyToAvailableCells(configureAvatar)
|
||||
applyToAvailableCells(configureIcon)
|
||||
}
|
||||
|
||||
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
|
||||
applyToAvailableCells(configureAvatar)
|
||||
applyToAvailableCells(configureIcon)
|
||||
}
|
||||
|
||||
@objc func feedSettingDidChange(_ note: Notification) {
|
||||
@ -640,7 +640,7 @@ private extension MasterFeedViewController {
|
||||
|
||||
cell.name = nameFor(node)
|
||||
cell.unreadCount = coordinator.unreadCountFor(node)
|
||||
configureAvatar(cell, node)
|
||||
configureIcon(cell, node)
|
||||
|
||||
guard let indexPath = dataSource.indexPath(for: node) else { return }
|
||||
let rowsInSection = tableView.numberOfRows(inSection: indexPath.section)
|
||||
@ -652,11 +652,11 @@ private extension MasterFeedViewController {
|
||||
|
||||
}
|
||||
|
||||
func configureAvatar(_ cell: MasterFeedTableViewCell, _ node: Node) {
|
||||
cell.avatarImage = imageFor(node)
|
||||
func configureIcon(_ cell: MasterFeedTableViewCell, _ node: Node) {
|
||||
cell.iconImage = imageFor(node)
|
||||
}
|
||||
|
||||
func imageFor(_ node: Node) -> UIImage? {
|
||||
func imageFor(_ node: Node) -> IconImage? {
|
||||
if let feed = node.representedObject as? Feed {
|
||||
|
||||
let feedIconImage = appDelegate.feedIconDownloader.icon(for: feed)
|
||||
|
@ -14,7 +14,7 @@ struct MasterTimelineAccessibilityCellLayout: MasterTimelineCellLayout {
|
||||
let height: CGFloat
|
||||
let unreadIndicatorRect: CGRect
|
||||
let starRect: CGRect
|
||||
let avatarImageRect: CGRect
|
||||
let iconImageRect: CGRect
|
||||
let titleRect: CGRect
|
||||
let summaryRect: CGRect
|
||||
let feedNameRect: CGRect
|
||||
@ -37,12 +37,12 @@ struct MasterTimelineAccessibilityCellLayout: MasterTimelineCellLayout {
|
||||
// Separator Insets
|
||||
self.separatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
|
||||
|
||||
// Avatar
|
||||
if cellData.showAvatar {
|
||||
self.avatarImageRect = MasterTimelineAccessibilityCellLayout.rectForAvatar(currentPoint)
|
||||
currentPoint.y = self.avatarImageRect.maxY
|
||||
// Icon Image
|
||||
if cellData.showIcon {
|
||||
self.iconImageRect = MasterTimelineAccessibilityCellLayout.rectForIconView(currentPoint)
|
||||
currentPoint.y = self.iconImageRect.maxY
|
||||
} else {
|
||||
self.avatarImageRect = CGRect.zero
|
||||
self.iconImageRect = CGRect.zero
|
||||
}
|
||||
|
||||
let textAreaWidth = width - (currentPoint.x + MasterTimelineDefaultCellLayout.cellPadding.right + insets.right)
|
||||
|
@ -16,14 +16,14 @@ struct MasterTimelineCellData {
|
||||
let dateString: String
|
||||
let feedName: String
|
||||
let showFeedName: Bool
|
||||
let avatar: UIImage? // feed icon, user avatar, or favicon
|
||||
let showAvatar: Bool // Make space even when avatar is nil
|
||||
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
|
||||
let read: Bool
|
||||
let starred: Bool
|
||||
let numberOfLines: Int
|
||||
|
||||
init(article: Article, showFeedName: Bool, feedName: String?, avatar: UIImage?, showAvatar: Bool, featuredImage: UIImage?, numberOfLines: Int) {
|
||||
init(article: Article, showFeedName: Bool, feedName: String?, iconImage: IconImage?, showIcon: Bool, featuredImage: UIImage?, numberOfLines: Int) {
|
||||
|
||||
self.title = ArticleStringFormatter.truncatedTitle(article)
|
||||
self.summary = ArticleStringFormatter.truncatedSummary(article)
|
||||
@ -39,8 +39,8 @@ struct MasterTimelineCellData {
|
||||
|
||||
self.showFeedName = showFeedName
|
||||
|
||||
self.showAvatar = showAvatar
|
||||
self.avatar = avatar
|
||||
self.showIcon = showIcon
|
||||
self.iconImage = iconImage
|
||||
self.featuredImage = featuredImage
|
||||
|
||||
self.read = article.status.read
|
||||
@ -55,8 +55,8 @@ struct MasterTimelineCellData {
|
||||
self.dateString = ""
|
||||
self.feedName = ""
|
||||
self.showFeedName = false
|
||||
self.showAvatar = false
|
||||
self.avatar = nil
|
||||
self.showIcon = false
|
||||
self.iconImage = nil
|
||||
self.featuredImage = nil
|
||||
self.read = true
|
||||
self.starred = false
|
||||
|
@ -13,7 +13,7 @@ protocol MasterTimelineCellLayout {
|
||||
var height: CGFloat {get}
|
||||
var unreadIndicatorRect: CGRect {get}
|
||||
var starRect: CGRect {get}
|
||||
var avatarImageRect: CGRect {get}
|
||||
var iconImageRect: CGRect {get}
|
||||
var titleRect: CGRect {get}
|
||||
var summaryRect: CGRect {get}
|
||||
var feedNameRect: CGRect {get}
|
||||
@ -42,9 +42,9 @@ extension MasterTimelineCellLayout {
|
||||
return r
|
||||
}
|
||||
|
||||
static func rectForAvatar(_ point: CGPoint) -> CGRect {
|
||||
static func rectForIconView(_ point: CGPoint) -> CGRect {
|
||||
var r = CGRect.zero
|
||||
r.size = MasterTimelineDefaultCellLayout.avatarSize
|
||||
r.size = MasterTimelineDefaultCellLayout.iconImageSize
|
||||
r.origin.x = point.x
|
||||
r.origin.y = point.y + 4
|
||||
return r
|
||||
|
@ -21,9 +21,9 @@ struct MasterTimelineDefaultCellLayout: MasterTimelineCellLayout {
|
||||
static let starDimension = CGFloat(integerLiteral: 16)
|
||||
static let starSize = CGSize(width: MasterTimelineDefaultCellLayout.starDimension, height: MasterTimelineDefaultCellLayout.starDimension)
|
||||
|
||||
static let avatarSize = CGSize(width: 32.0, height: 32.0)
|
||||
static let avatarMarginRight = CGFloat(integerLiteral: 8)
|
||||
static let avatarCornerRadius = CGFloat(integerLiteral: 4)
|
||||
static let iconImageSize = CGSize(width: 32.0, height: 32.0)
|
||||
static let iconMarginRight = CGFloat(integerLiteral: 8)
|
||||
static let iconCornerRadius = CGFloat(integerLiteral: 4)
|
||||
|
||||
static var titleFont: UIFont {
|
||||
return UIFont.preferredFont(forTextStyle: .headline)
|
||||
@ -47,7 +47,7 @@ struct MasterTimelineDefaultCellLayout: MasterTimelineCellLayout {
|
||||
let height: CGFloat
|
||||
let unreadIndicatorRect: CGRect
|
||||
let starRect: CGRect
|
||||
let avatarImageRect: CGRect
|
||||
let iconImageRect: CGRect
|
||||
let titleRect: CGRect
|
||||
let summaryRect: CGRect
|
||||
let feedNameRect: CGRect
|
||||
@ -70,12 +70,12 @@ struct MasterTimelineDefaultCellLayout: MasterTimelineCellLayout {
|
||||
// Separator Insets
|
||||
self.separatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
|
||||
|
||||
// Avatar
|
||||
if cellData.showAvatar {
|
||||
self.avatarImageRect = MasterTimelineDefaultCellLayout.rectForAvatar(currentPoint)
|
||||
currentPoint.x = self.avatarImageRect.maxX + MasterTimelineDefaultCellLayout.avatarMarginRight
|
||||
// Icon Image
|
||||
if cellData.showIcon {
|
||||
self.iconImageRect = MasterTimelineDefaultCellLayout.rectForIconView(currentPoint)
|
||||
currentPoint.x = self.iconImageRect.maxX + MasterTimelineDefaultCellLayout.iconMarginRight
|
||||
} else {
|
||||
self.avatarImageRect = CGRect.zero
|
||||
self.iconImageRect = CGRect.zero
|
||||
}
|
||||
|
||||
let textAreaWidth = width - (currentPoint.x + MasterTimelineDefaultCellLayout.cellPadding.right + insets.right)
|
||||
@ -98,7 +98,7 @@ struct MasterTimelineDefaultCellLayout: MasterTimelineCellLayout {
|
||||
let feedNameWidth = textAreaWidth - (MasterTimelineDefaultCellLayout.feedRightMargin + self.dateRect.size.width)
|
||||
self.feedNameRect = MasterTimelineDefaultCellLayout.rectForFeedName(cellData, currentPoint, feedNameWidth)
|
||||
|
||||
self.height = [self.avatarImageRect, self.feedNameRect].maxY() + MasterTimelineDefaultCellLayout.cellPadding.bottom
|
||||
self.height = [self.iconImageRect, self.feedNameRect].maxY() + MasterTimelineDefaultCellLayout.cellPadding.bottom
|
||||
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ class MasterTimelineTableViewCell: VibrantTableViewCell {
|
||||
private let dateView = MasterTimelineTableViewCell.singleLineUILabel()
|
||||
private let feedNameView = MasterTimelineTableViewCell.singleLineUILabel()
|
||||
|
||||
private lazy var avatarView = AvatarView()
|
||||
private lazy var iconView = IconView()
|
||||
|
||||
private lazy var starView = {
|
||||
return NonIntrinsicImageView(image: AppAssets.timelineStarImage)
|
||||
@ -68,7 +68,7 @@ class MasterTimelineTableViewCell: VibrantTableViewCell {
|
||||
|
||||
unreadIndicatorView.setFrameIfNotEqual(layout.unreadIndicatorRect)
|
||||
starView.setFrameIfNotEqual(layout.starRect)
|
||||
avatarView.setFrameIfNotEqual(layout.avatarImageRect)
|
||||
iconView.setFrameIfNotEqual(layout.iconImageRect)
|
||||
setFrame(for: titleView, rect: layout.titleRect)
|
||||
setFrame(for: summaryView, rect: layout.summaryRect)
|
||||
feedNameView.setFrameIfNotEqual(layout.feedNameRect)
|
||||
@ -77,8 +77,8 @@ class MasterTimelineTableViewCell: VibrantTableViewCell {
|
||||
separatorInset = layout.separatorInsets
|
||||
}
|
||||
|
||||
func setAvatarImage(_ image: UIImage) {
|
||||
avatarView.image = image
|
||||
func setIconImage(_ image: IconImage) {
|
||||
iconView.iconImage = image
|
||||
}
|
||||
|
||||
}
|
||||
@ -128,7 +128,7 @@ private extension MasterTimelineTableViewCell {
|
||||
addSubviewAtInit(unreadIndicatorView, hidden: true)
|
||||
addSubviewAtInit(dateView, hidden: false)
|
||||
addSubviewAtInit(feedNameView, hidden: true)
|
||||
addSubviewAtInit(avatarView, hidden: true)
|
||||
addSubviewAtInit(iconView, hidden: true)
|
||||
addSubviewAtInit(starView, hidden: true)
|
||||
}
|
||||
|
||||
@ -167,7 +167,6 @@ private extension MasterTimelineTableViewCell {
|
||||
}
|
||||
|
||||
func updateFeedNameView() {
|
||||
|
||||
if cellData.showFeedName {
|
||||
showView(feedNameView)
|
||||
feedNameView.font = MasterTimelineDefaultCellLayout.feedNameFont
|
||||
@ -187,28 +186,26 @@ private extension MasterTimelineTableViewCell {
|
||||
showOrHideView(starView, !cellData.starred)
|
||||
}
|
||||
|
||||
func updateAvatar() {
|
||||
|
||||
guard let image = cellData.avatar, cellData.showAvatar else {
|
||||
makeAvatarEmpty()
|
||||
func updateIconImage() {
|
||||
guard let image = cellData.iconImage, cellData.showIcon else {
|
||||
makeIconEmpty()
|
||||
return
|
||||
}
|
||||
|
||||
showView(avatarView)
|
||||
showView(iconView)
|
||||
|
||||
if avatarView.image !== cellData.avatar {
|
||||
avatarView.image = image
|
||||
if iconView.iconImage !== cellData.iconImage {
|
||||
iconView.iconImage = image
|
||||
setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
func makeAvatarEmpty() {
|
||||
|
||||
if avatarView.image != nil {
|
||||
avatarView.image = nil
|
||||
func makeIconEmpty() {
|
||||
if iconView.iconImage != nil {
|
||||
iconView.iconImage = nil
|
||||
setNeedsLayout()
|
||||
}
|
||||
hideView(avatarView)
|
||||
hideView(iconView)
|
||||
}
|
||||
|
||||
func hideView(_ view: UIView) {
|
||||
@ -234,7 +231,7 @@ private extension MasterTimelineTableViewCell {
|
||||
updateFeedNameView()
|
||||
updateUnreadIndicator()
|
||||
updateStarView()
|
||||
updateAvatar()
|
||||
updateIconImage()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import UIKit
|
||||
|
||||
class MasterTimelineTitleView: UIView {
|
||||
|
||||
@IBOutlet weak var avatarView: AvatarView!
|
||||
@IBOutlet weak var iconView: IconView!
|
||||
@IBOutlet weak var label: UILabel!
|
||||
@IBOutlet weak var unreadCountView: MasterTimelineUnreadCountView!
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
||||
<rect key="frame" x="79.5" y="9" width="110.5" height="20"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5gI-Wl-lnK" customClass="AvatarView" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5gI-Wl-lnK" customClass="IconView" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="9" width="20" height="20"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
@ -47,7 +47,7 @@
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
|
||||
<connections>
|
||||
<outlet property="avatarView" destination="5gI-Wl-lnK" id="u0l-N7-Kbp"/>
|
||||
<outlet property="iconView" destination="5gI-Wl-lnK" id="IiR-qS-d22"/>
|
||||
<outlet property="label" destination="5F6-2v-qSS" id="ec7-8Y-PRv"/>
|
||||
<outlet property="unreadCountView" destination="z9o-XA-3t4" id="JBy-aa-feL"/>
|
||||
</connections>
|
||||
|
@ -310,7 +310,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
||||
}
|
||||
|
||||
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
|
||||
titleView?.avatarView.image = coordinator.timelineAvatar
|
||||
titleView?.iconView.iconImage = coordinator.timelineIconImage
|
||||
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
|
||||
return
|
||||
}
|
||||
@ -318,14 +318,14 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
||||
guard let article = dataSource.itemIdentifier(for: indexPath) else {
|
||||
return
|
||||
}
|
||||
if article.feed == feed, let cell = tableView.cellForRow(at: indexPath) as? MasterTimelineTableViewCell, let image = avatarFor(article) {
|
||||
cell.setAvatarImage(image)
|
||||
if article.feed == feed, let cell = tableView.cellForRow(at: indexPath) as? MasterTimelineTableViewCell, let image = iconImageFor(article) {
|
||||
cell.setIconImage(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func avatarDidBecomeAvailable(_ note: Notification) {
|
||||
guard coordinator.showAvatars, let avatarURL = note.userInfo?[UserInfoKey.url] as? String else {
|
||||
guard coordinator.showIcons, let avatarURL = note.userInfo?[UserInfoKey.url] as? String else {
|
||||
return
|
||||
}
|
||||
tableView.indexPathsForVisibleRows?.forEach { indexPath in
|
||||
@ -333,16 +333,16 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
||||
return
|
||||
}
|
||||
for author in authors {
|
||||
if author.avatarURL == avatarURL, let cell = tableView.cellForRow(at: indexPath) as? MasterTimelineTableViewCell, let image = avatarFor(article) {
|
||||
cell.setAvatarImage(image)
|
||||
if author.avatarURL == avatarURL, let cell = tableView.cellForRow(at: indexPath) as? MasterTimelineTableViewCell, let image = iconImageFor(article) {
|
||||
cell.setIconImage(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func faviconDidBecomeAvailable(_ note: Notification) {
|
||||
titleView?.avatarView.image = coordinator.timelineAvatar
|
||||
if coordinator.showAvatars {
|
||||
titleView?.iconView.iconImage = coordinator.timelineIconImage
|
||||
if coordinator.showIcons {
|
||||
queueReloadAvailableCells()
|
||||
}
|
||||
}
|
||||
@ -392,7 +392,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
||||
let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, userDeleted: false, dateArrived: Date())
|
||||
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, feedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status)
|
||||
|
||||
let prototypeCellData = MasterTimelineCellData(article: prototypeArticle, showFeedName: true, feedName: "Prototype Feed Name", avatar: nil, showAvatar: false, featuredImage: nil, numberOfLines: numberOfTextLines)
|
||||
let prototypeCellData = MasterTimelineCellData(article: prototypeArticle, showFeedName: true, feedName: "Prototype Feed Name", iconImage: nil, showIcon: false, featuredImage: nil, numberOfLines: numberOfTextLines)
|
||||
|
||||
if UIApplication.shared.preferredContentSizeCategory.isAccessibilityCategory {
|
||||
let layout = MasterTimelineAccessibilityCellLayout(width: tableView.bounds.width, insets: tableView.safeAreaInsets, cellData: prototypeCellData)
|
||||
@ -448,7 +448,7 @@ private extension MasterTimelineViewController {
|
||||
if let titleView = Bundle.main.loadNibNamed("MasterTimelineTitleView", owner: self, options: nil)?[0] as? MasterTimelineTitleView {
|
||||
self.titleView = titleView
|
||||
|
||||
titleView.avatarView.image = coordinator.timelineAvatar
|
||||
titleView.iconView.iconImage = coordinator.timelineIconImage
|
||||
titleView.label.text = coordinator.timelineName
|
||||
updateTitleUnreadCount()
|
||||
|
||||
@ -508,20 +508,20 @@ private extension MasterTimelineViewController {
|
||||
|
||||
func configure(_ cell: MasterTimelineTableViewCell, article: Article) {
|
||||
|
||||
let avatar = avatarFor(article)
|
||||
let iconImage = iconImageFor(article)
|
||||
let featuredImage = featuredImageFor(article)
|
||||
|
||||
let showFeedNames = coordinator.showFeedNames
|
||||
let showAvatar = coordinator.showAvatars && avatar != nil
|
||||
cell.cellData = MasterTimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, avatar: avatar, showAvatar: showAvatar, featuredImage: featuredImage, numberOfLines: numberOfTextLines)
|
||||
let showIcon = coordinator.showIcons && iconImage != nil
|
||||
cell.cellData = MasterTimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, iconImage: iconImage, showIcon: showIcon, featuredImage: featuredImage, numberOfLines: numberOfTextLines)
|
||||
|
||||
}
|
||||
|
||||
func avatarFor(_ article: Article) -> RSImage? {
|
||||
if !coordinator.showAvatars {
|
||||
func iconImageFor(_ article: Article) -> IconImage? {
|
||||
if !coordinator.showIcons {
|
||||
return nil
|
||||
}
|
||||
return article.avatarImage()
|
||||
return article.iconImage()
|
||||
}
|
||||
|
||||
func featuredImageFor(_ article: Article) -> UIImage? {
|
||||
|
@ -111,7 +111,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
|
||||
private(set) var currentFeedIndexPath: IndexPath?
|
||||
|
||||
var timelineAvatar: RSImage? {
|
||||
var timelineIconImage: IconImage? {
|
||||
if let feed = timelineFetcher as? Feed {
|
||||
|
||||
let feedIconImage = appDelegate.feedIconDownloader.icon(for: feed)
|
||||
@ -119,8 +119,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
return feedIconImage
|
||||
}
|
||||
|
||||
if let faviconImage = appDelegate.faviconDownloader.faviconAsAvatar(for: feed) {
|
||||
return faviconImage
|
||||
if let faviconIconImage = appDelegate.faviconDownloader.faviconAsIcon(for: feed) {
|
||||
return faviconIconImage
|
||||
}
|
||||
|
||||
}
|
||||
@ -154,7 +154,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
}
|
||||
|
||||
private(set) var showFeedNames = false
|
||||
private(set) var showAvatars = false
|
||||
private(set) var showIcons = false
|
||||
|
||||
var isPrevFeedAvailable: Bool {
|
||||
guard let indexPath = currentFeedIndexPath else {
|
||||
@ -1087,10 +1087,10 @@ private extension SceneCoordinator {
|
||||
return indexPathFor(node)
|
||||
}
|
||||
|
||||
func updateShowAvatars() {
|
||||
func updateShowIcons() {
|
||||
|
||||
if showFeedNames {
|
||||
self.showAvatars = true
|
||||
self.showIcons = true
|
||||
return
|
||||
}
|
||||
|
||||
@ -1098,14 +1098,14 @@ private extension SceneCoordinator {
|
||||
if let authors = article.authors {
|
||||
for author in authors {
|
||||
if author.avatarURL != nil {
|
||||
self.showAvatars = true
|
||||
self.showIcons = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.showAvatars = false
|
||||
self.showIcons = false
|
||||
}
|
||||
|
||||
// MARK: Select Prev Unread
|
||||
@ -1350,7 +1350,7 @@ private extension SceneCoordinator {
|
||||
if articles != sortedArticles {
|
||||
|
||||
articles = sortedArticles
|
||||
updateShowAvatars()
|
||||
updateShowIcons()
|
||||
updateUnreadCount()
|
||||
|
||||
masterTimelineViewController?.reloadArticles(animate: animate)
|
||||
|
Loading…
x
Reference in New Issue
Block a user