From 8a2d1f5f6a05a0952692e1e3a6b8f6a7c325ee59 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 15 Sep 2019 23:02:13 -0700 Subject: [PATCH 1/7] Create TimelineAvatarView, which draws the background for images that need it. --- .../Timeline/Cell/TimelineAvatarView.swift | 118 ++++++++++++++++++ NetNewsWire.xcodeproj/project.pbxproj | 16 ++- Shared/Extensions/RSImage-Extensions.swift | 6 +- 3 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 Mac/MainWindow/Timeline/Cell/TimelineAvatarView.swift diff --git a/Mac/MainWindow/Timeline/Cell/TimelineAvatarView.swift b/Mac/MainWindow/Timeline/Cell/TimelineAvatarView.swift new file mode 100644 index 000000000..f6f5192ef --- /dev/null +++ b/Mac/MainWindow/Timeline/Cell/TimelineAvatarView.swift @@ -0,0 +1,118 @@ +// +// TimelineAvatarView.swift +// NetNewsWire +// +// Created by Brent Simmons on 9/15/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import AppKit + +final class TimelineAvatarView: NSView { + + var image: NSImage? = nil { + didSet { + imageView.image = image + updateHasExposedBackground() + needsDisplay = true + needsLayout = true + } + } + + override var isFlipped: Bool { + return true + } + + private let imageView: NSImageView = { + let imageView = NSImageView(frame: NSRect.zero) + imageView.animates = false + imageView.imageAlignment = .alignCenter + imageView.imageScaling = .scaleProportionallyDown + return imageView + }() + + private var hasExposedBackground = true { + didSet { + if oldValue != hasExposedBackground { + needsDisplay = true + } + } + } + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + commonInit() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + commonInit() + } + + convenience init() { + self.init(frame: NSRect.zero) + } + + override func viewDidMoveToSuperview() { + needsLayout = true + needsDisplay = true + } + + override func layout() { + resizeSubviews(withOldSize: NSZeroSize) + } + + override func resizeSubviews(withOldSize oldSize: NSSize) { + imageView.rs_setFrameIfNotEqual(rectForImageView()) + } + + override func draw(_ dirtyRect: NSRect) { + guard hasExposedBackground else { + return + } + let rImage = imageView.frame + if rImage.contains(dirtyRect) { + return + } + + let color = NSApplication.shared.effectiveAppearance.isDarkMode ? AppAssets.avatarDarkBackgroundColor : AppAssets.avatarLightBackgroundColor + color.set() + dirtyRect.fill() + } +} + +private extension TimelineAvatarView { + + func commonInit() { + addSubview(imageView) + } + + func rectForImageView() -> NSRect { + guard let image = image else { + return NSRect.zero + } + + let imageSize = image.size + let viewSize = bounds.size + if imageSize.height == imageSize.width { + return NSMakeRect(0.0, 0.0, viewSize.width, viewSize.height) + } + else if imageSize.height > imageSize.width { + let factor = viewSize.height / imageSize.height + let width = imageSize.width * factor + let originX = floor((viewSize.width - width) / 2.0) + return NSMakeRect(originX, 0.0, width, viewSize.height) + } + + // Wider than tall: imageSize.width > imageSize.height + let factor = viewSize.width / imageSize.width + let height = imageSize.height * factor + let originY = floor((viewSize.height - height) / 2.0) + return NSMakeRect(0.0, originY, viewSize.width, height) + } + + func updateHasExposedBackground() { + let rImage = rectForImageView() + hasExposedBackground = rImage.size.height < bounds.size.height || rImage.size.width < bounds.size.width + } +} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index c13da6839..64b281494 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -187,6 +187,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 */; }; 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 */; }; @@ -797,6 +798,7 @@ 8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = ""; }; 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 = ""; }; + 847CD6C9232F4CBF00FAC46D /* TimelineAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineAvatarView.swift; sourceTree = ""; }; 847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAppleEventDescriptor+UserRecordFields.swift"; sourceTree = ""; }; 848362FC2262A30800DA1D35 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; sourceTree = ""; }; 848362FE2262A30E00DA1D35 /* template.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = template.html; sourceTree = ""; }; @@ -1422,6 +1424,7 @@ 84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */, 849A97711ED9EC04007D329B /* TimelineCellData.swift */, 849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */, + 847CD6C9232F4CBF00FAC46D /* TimelineAvatarView.swift */, ); path = Cell; sourceTree = ""; @@ -1960,12 +1963,12 @@ ORGANIZATIONNAME = "Ranchero Software"; TargetAttributes = { 6581C73220CED60000F4AD34 = { - DevelopmentTeam = SHJK2V3AJG; - ProvisioningStyle = Automatic; + DevelopmentTeam = M8L2WTLA8W; + ProvisioningStyle = Manual; }; 840D617B2029031C009BC708 = { CreatedOnToolsVersion = 9.3; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = M8L2WTLA8W; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { @@ -1981,8 +1984,8 @@ }; 849C645F1ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = SHJK2V3AJG; - ProvisioningStyle = Automatic; + DevelopmentTeam = M8L2WTLA8W; + ProvisioningStyle = Manual; SystemCapabilities = { com.apple.HardenedRuntime = { enabled = 1; @@ -1991,7 +1994,7 @@ }; 849C64701ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = 9C84TZ7Q6Z; ProvisioningStyle = Automatic; TestTargetID = 849C645F1ED37A5D003D8FC0; }; @@ -2430,6 +2433,7 @@ files = ( 84F204E01FAACBB30076E152 /* ArticleArray.swift in Sources */, 848B937221C8C5540038DC0D /* CrashReporter.swift in Sources */, + 847CD6CA232F4CBF00FAC46D /* TimelineAvatarView.swift in Sources */, 84BBB12E20142A4700F054F5 /* InspectorWindowController.swift in Sources */, 51EF0F7A22771B890050506E /* ColorHash.swift in Sources */, 84E46C7D1F75EF7B005ECFB3 /* AppDefaults.swift in Sources */, diff --git a/Shared/Extensions/RSImage-Extensions.swift b/Shared/Extensions/RSImage-Extensions.swift index 3406a959b..0b566b632 100644 --- a/Shared/Extensions/RSImage-Extensions.swift +++ b/Shared/Extensions/RSImage-Extensions.swift @@ -28,9 +28,9 @@ extension RSImage { return nil } - if cgImage.width < avatarSize || cgImage.height < avatarSize { - cgImage = RSImage.compositeAvatar(cgImage) - } +// if cgImage.width < avatarSize || cgImage.height < avatarSize { +// cgImage = RSImage.compositeAvatar(cgImage) +// } #if os(iOS) return RSImage(cgImage: cgImage) From 2f9f4e263c567d2407fbd8d958a528d2dbe75e48 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Mon, 16 Sep 2019 19:59:33 -0700 Subject: [PATCH 2/7] Restore color for light avatar background. --- .../avatarLightBackgroundColor.colorset/Contents.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mac/Resources/Assets.xcassets/avatarLightBackgroundColor.colorset/Contents.json b/Mac/Resources/Assets.xcassets/avatarLightBackgroundColor.colorset/Contents.json index 57a5cc49d..af234f435 100644 --- a/Mac/Resources/Assets.xcassets/avatarLightBackgroundColor.colorset/Contents.json +++ b/Mac/Resources/Assets.xcassets/avatarLightBackgroundColor.colorset/Contents.json @@ -17,4 +17,4 @@ } } ] -} \ No newline at end of file +} From 506b621e5dd3c43478640ff17b103a2f3b90a963 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Mon, 16 Sep 2019 20:00:32 -0700 Subject: [PATCH 3/7] Draw the avatar background in TimelineAvatarView only if the icon is too short vertically. --- .../Timeline/Cell/TimelineAvatarView.swift | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/Mac/MainWindow/Timeline/Cell/TimelineAvatarView.swift b/Mac/MainWindow/Timeline/Cell/TimelineAvatarView.swift index f6f5192ef..e84ec4a08 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineAvatarView.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineAvatarView.swift @@ -12,10 +12,11 @@ final class TimelineAvatarView: NSView { var image: NSImage? = nil { didSet { - imageView.image = image - updateHasExposedBackground() - needsDisplay = true - needsLayout = true + if image !== oldValue { + imageView.image = image + needsDisplay = true + needsLayout = true + } } } @@ -27,18 +28,17 @@ final class TimelineAvatarView: NSView { let imageView = NSImageView(frame: NSRect.zero) imageView.animates = false imageView.imageAlignment = .alignCenter - imageView.imageScaling = .scaleProportionallyDown + imageView.imageScaling = .scaleProportionallyUpOrDown return imageView }() - private var hasExposedBackground = true { - didSet { - if oldValue != hasExposedBackground { - needsDisplay = true - } - } + private var hasExposedVerticalBackground: Bool { + return imageView.frame.size.height < bounds.size.height } + private static var lightBackgroundColor = AppAssets.avatarLightBackgroundColor + private static var darkBackgroundColor = AppAssets.avatarDarkBackgroundColor + override init(frame frameRect: NSRect) { super.init(frame: frameRect) commonInit() @@ -67,15 +67,11 @@ final class TimelineAvatarView: NSView { } override func draw(_ dirtyRect: NSRect) { - guard hasExposedBackground else { - return - } - let rImage = imageView.frame - if rImage.contains(dirtyRect) { + guard hasExposedVerticalBackground else { return } - let color = NSApplication.shared.effectiveAppearance.isDarkMode ? AppAssets.avatarDarkBackgroundColor : AppAssets.avatarLightBackgroundColor + let color = NSApplication.shared.effectiveAppearance.isDarkMode ? TimelineAvatarView.darkBackgroundColor : TimelineAvatarView.lightBackgroundColor color.set() dirtyRect.fill() } @@ -85,6 +81,7 @@ private extension TimelineAvatarView { func commonInit() { addSubview(imageView) + wantsLayer = true } func rectForImageView() -> NSRect { @@ -95,7 +92,12 @@ private extension TimelineAvatarView { let imageSize = image.size let viewSize = bounds.size if imageSize.height == imageSize.width { - return NSMakeRect(0.0, 0.0, viewSize.width, viewSize.height) + if imageSize.height >= viewSize.height * 0.75 { + // Close enough to viewSize to scale up the image. + return NSMakeRect(0.0, 0.0, viewSize.width, viewSize.height) + } + let offset = floor((viewSize.height - imageSize.height) / 2.0) + return NSMakeRect(offset, offset, imageSize.width, imageSize.height) } else if imageSize.height > imageSize.width { let factor = viewSize.height / imageSize.height @@ -110,9 +112,4 @@ private extension TimelineAvatarView { let originY = floor((viewSize.height - height) / 2.0) return NSMakeRect(0.0, originY, viewSize.width, height) } - - func updateHasExposedBackground() { - let rImage = rectForImageView() - hasExposedBackground = rImage.size.height < bounds.size.height || rImage.size.width < bounds.size.width - } } From a225d52ba3bb03419fb0bed3b95edf97628c5072 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Mon, 16 Sep 2019 20:01:30 -0700 Subject: [PATCH 4/7] Use the new TimelineAvatarView instead of an NSImageView in TimelineTableCellView. --- .../Timeline/Cell/TimelineTableCellView.swift | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift index 680b5e2db..22a1628dd 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift @@ -18,13 +18,7 @@ class TimelineTableCellView: NSTableCellView { private let dateView = TimelineTableCellView.singleLineTextField() private let feedNameView = TimelineTableCellView.singleLineTextField() - private lazy var avatarImageView: NSImageView = { - let imageView = TimelineTableCellView.imageView(with: AppAssets.genericFeedImage, scaling: .scaleNone) - imageView.imageAlignment = .alignTop - imageView.imageScaling = .scaleProportionallyDown - imageView.wantsLayer = true - return imageView - }() + private lazy var avatarView = TimelineAvatarView() private let starView = TimelineTableCellView.imageView(with: AppAssets.timelineStar, scaling: .scaleNone) private let separatorView = TimelineTableCellView.separatorView() @@ -43,7 +37,7 @@ class TimelineTableCellView: NSTableCellView { didSet { if cellAppearance != oldValue { updateTextFieldFonts() - avatarImageView.layer?.cornerRadius = cellAppearance.avatarCornerRadius + avatarView.layer?.cornerRadius = cellAppearance.avatarCornerRadius needsLayout = true } } @@ -125,7 +119,7 @@ class TimelineTableCellView: NSTableCellView { dateView.rs_setFrameIfNotEqual(layoutRects.dateRect) unreadIndicatorView.rs_setFrameIfNotEqual(layoutRects.unreadIndicatorRect) feedNameView.rs_setFrameIfNotEqual(layoutRects.feedNameRect) - avatarImageView.rs_setFrameIfNotEqual(layoutRects.avatarImageRect) + avatarView.rs_setFrameIfNotEqual(layoutRects.avatarImageRect) starView.rs_setFrameIfNotEqual(layoutRects.starRect) separatorView.rs_setFrameIfNotEqual(layoutRects.separatorRect) } @@ -213,7 +207,7 @@ private extension TimelineTableCellView { addSubviewAtInit(unreadIndicatorView, hidden: true) addSubviewAtInit(dateView, hidden: false) addSubviewAtInit(feedNameView, hidden: true) - addSubviewAtInit(avatarImageView, hidden: true) + addSubviewAtInit(avatarView, hidden: true) addSubviewAtInit(starView, hidden: true) addSubviewAtInit(separatorView, hidden: !AppDefaults.timelineShowsSeparators) @@ -222,7 +216,7 @@ private extension TimelineTableCellView { func updatedLayoutRects() -> TimelineCellLayout { - return TimelineCellLayout(width: bounds.width, height: bounds.height, cellData: cellData, appearance: cellAppearance, hasAvatar: avatarImageView.image != nil) + return TimelineCellLayout(width: bounds.width, height: bounds.height, cellData: cellData, appearance: cellAppearance, hasAvatar: avatarView.image != nil) } func updateTitleView() { @@ -277,19 +271,19 @@ private extension TimelineTableCellView { return } - showView(avatarImageView) - if avatarImageView.image !== image { - avatarImageView.image = image + showView(avatarView) + if avatarView.image !== image { + avatarView.image = image needsLayout = true } } func makeAvatarEmpty() { - if avatarImageView.image != nil { - avatarImageView.image = nil + if avatarView.image != nil { + avatarView.image = nil needsLayout = true } - hideView(avatarImageView) + hideView(avatarView) } func hideView(_ view: NSView) { From 2d22f061a5f0eda49a79bf1547671a06a6c3d21b Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Mon, 16 Sep 2019 20:07:07 -0700 Subject: [PATCH 5/7] Remove no-longer-needed functions for compositing the avatar on a background. --- Shared/Extensions/RSImage-Extensions.swift | 55 +--------------------- 1 file changed, 2 insertions(+), 53 deletions(-) diff --git a/Shared/Extensions/RSImage-Extensions.swift b/Shared/Extensions/RSImage-Extensions.swift index 0b566b632..a09cafeb7 100644 --- a/Shared/Extensions/RSImage-Extensions.swift +++ b/Shared/Extensions/RSImage-Extensions.swift @@ -27,64 +27,13 @@ extension RSImage { guard var cgImage = RSImage.scaleImage(data, maxPixelSize: scaledMaxPixelSize) else { return nil } - -// if cgImage.width < avatarSize || cgImage.height < avatarSize { -// cgImage = RSImage.compositeAvatar(cgImage) -// } - + #if os(iOS) return RSImage(cgImage: cgImage) #else let size = NSSize(width: cgImage.width, height: cgImage.height) return RSImage(cgImage: cgImage, size: size) - #endif - + #endif } - } -private extension RSImage { - - #if os(iOS) - - static func compositeAvatar(_ avatar: CGImage) -> CGImage { - let rect = CGRect(x: 0, y: 0, width: avatarSize, height: avatarSize) - UIGraphicsBeginImageContext(rect.size) - if let context = UIGraphicsGetCurrentContext() { - context.setFillColor(AppAssets.avatarLightBackgroundColor.cgColor) - context.fill(rect) - context.translateBy(x: 0.0, y: CGFloat(integerLiteral: avatarSize)); - context.scaleBy(x: 1.0, y: -1.0) - let avatarRect = CGRect(x: (avatarSize - avatar.width) / 2, y: (avatarSize - avatar.height) / 2, width: avatar.width, height: avatar.height) - context.draw(avatar, in: avatarRect) - } - let img = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext() - return img!.cgImage! - } - - #else - - static func compositeAvatar(_ avatar: CGImage) -> CGImage { - var resultRect = CGRect(x: 0, y: 0, width: avatarSize, height: avatarSize) - let resultImage = NSImage(size: resultRect.size) - - resultImage.lockFocus() - if let context = NSGraphicsContext.current?.cgContext { - if NSApplication.shared.effectiveAppearance.isDarkMode { - context.setFillColor(AppAssets.avatarDarkBackgroundColor.cgColor) - } else { - context.setFillColor(AppAssets.avatarLightBackgroundColor.cgColor) - } - context.fill(resultRect) - let avatarRect = CGRect(x: (avatarSize - avatar.width) / 2, y: (avatarSize - avatar.height) / 2, width: avatar.width, height: avatar.height) - context.draw(avatar, in: avatarRect) - } - resultImage.unlockFocus() - - return resultImage.cgImage(forProposedRect: &resultRect, context: nil, hints: nil)! - } - - #endif - -} From f7c9cc1dbdb1bcc63d12cf25771dd632232c70a5 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Mon, 16 Sep 2019 22:09:58 -0700 Subject: [PATCH 6/7] Remove no-longer needed machinery for updating the timeline on dark-mode/light-mode appearance changes. --- .../Timeline/TimelineViewController.swift | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index 4b770b8e6..b9c8df705 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -11,10 +11,6 @@ import RSCore import Articles import Account -extension Notification.Name { - static let AppleInterfaceThemeChangedNotification = Notification.Name("AppleInterfaceThemeChangedNotification") -} - protocol TimelineDelegate: class { func timelineSelectionDidChange(_: TimelineViewController, selectedArticles: [Article]?) } @@ -174,9 +170,8 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .AccountsDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(containerChildrenDidChange(_:)), name: .ChildrenDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil) - DistributedNotificationCenter.default.addObserver(self, selector: #selector(appleInterfaceThemeChanged), name: .AppleInterfaceThemeChangedNotification, object: nil) - didRegisterForNotifications = true + didRegisterForNotifications = true } } @@ -556,15 +551,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr self.sortDirection = AppDefaults.timelineSortDirection } - @objc func appleInterfaceThemeChanged(_ note: Notification) { - appDelegate.authorAvatarDownloader.resetCache() - appDelegate.feedIconDownloader.resetCache() - appDelegate.faviconDownloader.resetCache() - performBlockAndRestoreSelection { - tableView.reloadData() - } - } - // MARK: - Reloading Data private func cellForRowView(_ rowView: NSView) -> NSView? { From 66d933399942bbb64f6d7646810024b98777872d Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Mon, 16 Sep 2019 22:27:17 -0700 Subject: [PATCH 7/7] Make minor code formatting changes so that TimelineViewController matches our current thinking. Also delete some commented-out and unused code. --- .../Timeline/TimelineViewController.swift | 112 ++---------------- 1 file changed, 13 insertions(+), 99 deletions(-) diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index b9c8df705..ffa01d921 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -148,7 +148,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } override func viewDidLoad() { - cellAppearance = TimelineCellAppearance(showAvatar: false, fontSize: fontSize) cellAppearanceWithAvatar = TimelineCellAppearance(showAvatar: true, fontSize: fontSize) @@ -160,7 +159,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr tableView.keyboardDelegate = keyboardDelegate if !didRegisterForNotifications { - NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil) @@ -179,42 +177,9 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr sharingServiceDelegate = SharingServiceDelegate(self.view.window) } - // MARK: State Restoration - -// private static let stateRestorationSelectedArticles = "selectedArticles" -// -// override func encodeRestorableState(with coder: NSCoder) { -// -// super.encodeRestorableState(with: coder) -// -// coder.encode(self.selectedArticleIDs(), forKey: TimelineViewController.stateRestorationSelectedArticles) -// } -// -// override func restoreState(with coder: NSCoder) { -// -// super.restoreState(with: coder) -// -// if let restoredArticleIDs = (try? coder.decodeTopLevelObject(forKey: TimelineViewController.stateRestorationSelectedArticles)) as? [String] { -// self.restoreSelection(restoredArticleIDs) -// } -// } - - // MARK: Appearance Change - - private func fontSizeDidChange() { - - cellAppearance = TimelineCellAppearance(showAvatar: false, fontSize: fontSize) - cellAppearanceWithAvatar = TimelineCellAppearance(showAvatar: true, fontSize: fontSize) - updateRowHeights() - performBlockAndRestoreSelection { - tableView.reloadData() - } - } - // MARK: - API func markAllAsRead() { - guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articles, markingRead: true, undoManager: undoManager) else { return } @@ -222,12 +187,10 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } func canMarkAllAsRead() -> Bool { - return articles.canMarkAllAsRead() } func canMarkSelectedArticlesAsRead() -> Bool { - return selectedArticles.canMarkAllAsRead() } @@ -244,14 +207,12 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr // MARK: - Actions @objc func openArticleInBrowser(_ sender: Any?) { - if let link = oneSelectedArticle?.preferredLink { Browser.open(link) } } @IBAction func toggleStatusOfSelectedArticles(_ sender: Any?) { - guard !selectedArticles.isEmpty else { return } @@ -268,7 +229,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } @IBAction func markSelectedArticlesAsRead(_ sender: Any?) { - guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: true, undoManager: undoManager) else { return } @@ -276,7 +236,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } @IBAction func markSelectedArticlesAsUnread(_ sender: Any?) { - guard let undoManager = undoManager, let markUnreadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: false, undoManager: undoManager) else { return } @@ -284,12 +243,10 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } @IBAction func copy(_ sender: Any?) { - NSPasteboard.general.copyObjects(selectedArticles) } @IBAction func selectNextUp(_ sender: Any?) { - guard let lastSelectedRow = tableView.selectedRowIndexes.last else { return } @@ -311,7 +268,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } @IBAction func selectNextDown(_ sender: Any?) { - guard let firstSelectedRow = tableView.selectedRowIndexes.first else { return } @@ -334,7 +290,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } func toggleReadStatusForSelectedArticles() { - // If any one of the selected articles is unread, then mark them as read. // If all articles are read, then mark them as unread them. @@ -379,12 +334,10 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } func markStarredCommandStatus() -> MarkCommandValidationStatus { - return MarkCommandValidationStatus.statusFor(selectedArticles) { $0.anyArticleIsUnstarred() } } func markReadCommandStatus() -> MarkCommandValidationStatus { - return MarkCommandValidationStatus.statusFor(selectedArticles) { $0.anyArticleIsUnread() } } @@ -426,7 +379,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr // MARK: - Navigation func goToNextUnread() { - guard let ix = indexOfNextUnreadArticle() else { return } @@ -436,7 +388,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } func canGoToNextUnread() -> Bool { - guard let _ = indexOfNextUnreadArticle() else { return false } @@ -444,12 +395,10 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } func indexOfNextUnreadArticle() -> Int? { - return articles.rowOfNextUnreadArticle(tableView.selectedRow) } func focus() { - guard let window = tableView.window else { return } @@ -463,7 +412,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr // MARK: - Notifications @objc func statusesDidChange(_ note: Notification) { - guard let articles = note.userInfo?[Account.UserInfoKey.articles] as? Set
else { return } @@ -472,7 +420,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } @objc func feedIconDidBecomeAvailable(_ note: Notification) { - guard showAvatars, let feed = note.userInfo?[UserInfoKey.feed] as? Feed else { return } @@ -488,7 +435,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } @objc func avatarDidBecomeAvailable(_ note: Notification) { - guard showAvatars, let avatarURL = note.userInfo?[UserInfoKey.url] as? String else { return } @@ -516,7 +462,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } @objc func accountDidDownloadArticles(_ note: Notification) { - guard let feeds = note.userInfo?[Account.UserInfoKey.feeds] as? Set else { return } @@ -546,7 +491,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } @objc func userDefaultsDidChange(_ note: Notification) { - self.fontSize = AppDefaults.timelineFontSize self.sortDirection = AppDefaults.timelineSortDirection } @@ -554,7 +498,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr // MARK: - Reloading Data private func cellForRowView(_ rowView: NSView) -> NSView? { - for oneView in rowView.subviews where oneView is TimelineTableCellView { return oneView } @@ -603,7 +546,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr // MARK: - Cell Configuring private func calculateRowHeight(showingFeedNames: Bool) -> CGFloat { - let longTitle = "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?" let prototypeID = "prototype" let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, userDeleted: false, dateArrived: Date()) @@ -615,7 +557,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } private func updateRowHeights() { - rowHeightWithFeedName = calculateRowHeight(showingFeedNames: true) rowHeightWithoutFeedName = calculateRowHeight(showingFeedNames: false) updateTableViewRowHeight() @@ -663,7 +604,6 @@ extension TimelineViewController: NSMenuDelegate { extension TimelineViewController: NSUserInterfaceValidations { func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool { - if item.action == #selector(openArticleInBrowser(_:)) { let currentLink = oneSelectedArticle?.preferredLink return currentLink != nil @@ -680,7 +620,6 @@ extension TimelineViewController: NSUserInterfaceValidations { // MARK: - NSTableViewDataSource extension TimelineViewController: NSTableViewDataSource { - func numberOfRows(in tableView: NSTableView) -> Int { return articles.count } @@ -700,7 +639,6 @@ extension TimelineViewController: NSTableViewDataSource { // MARK: - NSTableViewDelegate extension TimelineViewController: NSTableViewDelegate { - private static let rowViewIdentifier = NSUserInterfaceItemIdentifier(rawValue: "timelineRow") func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? { @@ -751,8 +689,6 @@ extension TimelineViewController: NSTableViewDelegate { } selectionDidChange(selectedArticles) - -// self.invalidateRestorableState() } private func selectionDidChange(_ selectedArticles: ArticleArray?) { @@ -761,11 +697,8 @@ extension TimelineViewController: NSTableViewDelegate { private func configureTimelineCell(_ cell: TimelineTableCellView, article: Article) { cell.objectValue = article - let avatar = avatarFor(article) - let featuredImage = featuredImageFor(article) - - cell.cellData = TimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, avatar: avatar, showAvatar: showAvatars, featuredImage: featuredImage) + cell.cellData = TimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, avatar: avatar, showAvatar: showAvatars, featuredImage: nil) } private func avatarFor(_ article: Article) -> NSImage? { @@ -800,22 +733,6 @@ extension TimelineViewController: NSTableViewDelegate { return appDelegate.authorAvatarDownloader.image(for: author) } - private func featuredImageFor(_ article: Article) -> NSImage? { - // At this writing (17 June 2019) we’re not displaying featured images anywhere, - // so let’s skip downloading them even if we find them. - // - // We’ll revisit this later. - -// if let url = article.imageURL { -// if let imageData = appDelegate.imageDownloader.image(for: url) { -// return NSImage(data: imageData) -// } -// } - - return nil - - } - private func makeTimelineCellEmpty(_ cell: TimelineTableCellView) { cell.objectValue = nil cell.cellData = TimelineCellData() @@ -827,7 +744,6 @@ extension TimelineViewController: NSTableViewDelegate { private extension TimelineViewController { func startObservingUserDefaults() { - assert(timelineShowsSeparatorsObserver == nil) timelineShowsSeparatorsObserver = UserDefaults.standard.observe(\UserDefaults.CorreiaSeparators) { [weak self] (_, _) in guard let self = self, self.isViewLoaded else { return } @@ -840,7 +756,6 @@ private extension TimelineViewController { } @objc func reloadAvailableCells() { - if let indexesToReload = tableView.indexesOfAvailableRows() { reloadCells(for: indexesToReload) } @@ -857,17 +772,14 @@ private extension TimelineViewController { } func queueReloadAvailableCells() { - CoalescingQueue.standard.add(self, #selector(reloadAvailableCells)) } func updateTableViewRowHeight() { - tableView.rowHeight = currentRowHeight } func updateShowAvatars() { - if showFeedNames { self.showAvatars = true return @@ -894,7 +806,6 @@ private extension TimelineViewController { } func sortDirectionDidChange() { - performBlockAndRestoreSelection { let unsortedArticles = Set(articles) replaceArticles(with: unsortedArticles) @@ -902,12 +813,10 @@ private extension TimelineViewController { } func selectedArticleIDs() -> [String] { - return selectedArticles.articleIDs() } func restoreSelection(_ articleIDs: [String]) { - selectArticles(articleIDs) if tableView.selectedRow != -1 { tableView.scrollRowToVisible(tableView.selectedRow) @@ -915,7 +824,6 @@ private extension TimelineViewController { } func performBlockAndRestoreSelection(_ block: (() -> Void)) { - let savedSelection = selectedArticleIDs() block() restoreSelection(savedSelection) @@ -947,7 +855,6 @@ private extension TimelineViewController { } func indexesForArticleIDs(_ articleIDs: Set) -> IndexSet { - var indexes = IndexSet() articleIDs.forEach { (articleID) in @@ -962,7 +869,18 @@ private extension TimelineViewController { return indexes } - // MARK: Fetching Articles + // MARK: - Appearance Change + + private func fontSizeDidChange() { + cellAppearance = TimelineCellAppearance(showAvatar: false, fontSize: fontSize) + cellAppearanceWithAvatar = TimelineCellAppearance(showAvatar: true, fontSize: fontSize) + updateRowHeights() + performBlockAndRestoreSelection { + tableView.reloadData() + } + } + + // MARK: - Fetching Articles func fetchAndReplaceArticlesSync() { // To be called when the user has made a change of selection in the sidebar. @@ -1030,7 +948,6 @@ private extension TimelineViewController { } func selectArticles(_ articleIDs: [String]) { - let indexesToSelect = indexesForArticleIDs(Set(articleIDs)) if indexesToSelect.isEmpty { tableView.deselectAll(self) @@ -1040,12 +957,10 @@ private extension TimelineViewController { } func queueFetchAndMergeArticles() { - TimelineViewController.fetchAndMergeArticlesQueue.add(self, #selector(fetchAndMergeArticles)) } func representedObjectArraysAreEqual(_ objects1: [AnyObject]?, _ objects2: [AnyObject]?) -> Bool { - if objects1 == nil && objects2 == nil { return true } @@ -1079,7 +994,6 @@ private extension TimelineViewController { } func representedObjectsContainsAnyFeed(_ feeds: Set) -> Bool { - // Return true if there’s a match or if a folder contains (recursively) one of feeds guard let representedObjects = representedObjects else {