diff --git a/Mac/MainWindow/Timeline/Cell/TimelineAvatarView.swift b/Mac/MainWindow/Timeline/Cell/TimelineAvatarView.swift new file mode 100644 index 000000000..e84ec4a08 --- /dev/null +++ b/Mac/MainWindow/Timeline/Cell/TimelineAvatarView.swift @@ -0,0 +1,115 @@ +// +// 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 { + if image !== oldValue { + imageView.image = image + 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 = .scaleProportionallyUpOrDown + return imageView + }() + + 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() + } + + 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 hasExposedVerticalBackground else { + return + } + + let color = NSApplication.shared.effectiveAppearance.isDarkMode ? TimelineAvatarView.darkBackgroundColor : TimelineAvatarView.lightBackgroundColor + color.set() + dirtyRect.fill() + } +} + +private extension TimelineAvatarView { + + func commonInit() { + addSubview(imageView) + wantsLayer = true + } + + func rectForImageView() -> NSRect { + guard let image = image else { + return NSRect.zero + } + + let imageSize = image.size + let viewSize = bounds.size + if imageSize.height == imageSize.width { + 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 + 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) + } +} 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) { diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index 16a694d09..8fdb02647 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]?) } @@ -159,7 +155,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } override func viewDidLoad() { - cellAppearance = TimelineCellAppearance(showAvatar: false, fontSize: fontSize) cellAppearanceWithAvatar = TimelineCellAppearance(showAvatar: true, fontSize: fontSize) @@ -171,7 +166,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) @@ -182,9 +176,8 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .UserDidDeleteAccount, 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 } } @@ -192,42 +185,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 } @@ -235,12 +195,10 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } func canMarkAllAsRead() -> Bool { - return articles.canMarkAllAsRead() } func canMarkSelectedArticlesAsRead() -> Bool { - return selectedArticles.canMarkAllAsRead() } @@ -257,14 +215,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 } @@ -281,7 +237,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 } @@ -289,7 +244,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 } @@ -297,12 +251,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 } @@ -324,7 +276,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } @IBAction func selectNextDown(_ sender: Any?) { - guard let firstSelectedRow = tableView.selectedRowIndexes.first else { return } @@ -347,7 +298,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. @@ -392,12 +342,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() } } @@ -439,7 +387,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr // MARK: - Navigation func goToNextUnread() { - guard let ix = indexOfNextUnreadArticle() else { return } @@ -449,7 +396,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } func canGoToNextUnread() -> Bool { - guard let _ = indexOfNextUnreadArticle() else { return false } @@ -457,12 +403,10 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } func indexOfNextUnreadArticle() -> Int? { - return articles.rowOfNextUnreadArticle(tableView.selectedRow) } func focus() { - guard let window = tableView.window else { return } @@ -476,7 +420,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 } @@ -485,7 +428,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } @objc func feedIconDidBecomeAvailable(_ note: Notification) { - guard showAvatars, let feed = note.userInfo?[UserInfoKey.feed] as? Feed else { return } @@ -501,7 +443,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } @objc func avatarDidBecomeAvailable(_ note: Notification) { - guard showAvatars, let avatarURL = note.userInfo?[UserInfoKey.url] as? String else { return } @@ -529,7 +470,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } @objc func accountDidDownloadArticles(_ note: Notification) { - guard let feeds = note.userInfo?[Account.UserInfoKey.feeds] as? Set else { return } @@ -559,25 +499,14 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } @objc func userDefaultsDidChange(_ note: Notification) { - self.fontSize = AppDefaults.timelineFontSize self.sortDirection = AppDefaults.timelineSortDirection self.groupByFeed = AppDefaults.timelineGroupByFeed } - @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? { - for oneView in rowView.subviews where oneView is TimelineTableCellView { return oneView } @@ -626,7 +555,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()) @@ -638,7 +566,6 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } private func updateRowHeights() { - rowHeightWithFeedName = calculateRowHeight(showingFeedNames: true) rowHeightWithoutFeedName = calculateRowHeight(showingFeedNames: false) updateTableViewRowHeight() @@ -686,7 +613,6 @@ extension TimelineViewController: NSMenuDelegate { extension TimelineViewController: NSUserInterfaceValidations { func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool { - if item.action == #selector(openArticleInBrowser(_:)) { let currentLink = oneSelectedArticle?.preferredLink return currentLink != nil @@ -703,7 +629,6 @@ extension TimelineViewController: NSUserInterfaceValidations { // MARK: - NSTableViewDataSource extension TimelineViewController: NSTableViewDataSource { - func numberOfRows(in tableView: NSTableView) -> Int { return articles.count } @@ -723,7 +648,6 @@ extension TimelineViewController: NSTableViewDataSource { // MARK: - NSTableViewDelegate extension TimelineViewController: NSTableViewDelegate { - private static let rowViewIdentifier = NSUserInterfaceItemIdentifier(rawValue: "timelineRow") func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? { @@ -774,8 +698,6 @@ extension TimelineViewController: NSTableViewDelegate { } selectionDidChange(selectedArticles) - -// self.invalidateRestorableState() } private func selectionDidChange(_ selectedArticles: ArticleArray?) { @@ -784,27 +706,40 @@ extension TimelineViewController: NSTableViewDelegate { private func configureTimelineCell(_ cell: TimelineTableCellView, article: Article) { cell.objectValue = article - let avatar = article.avatarImage() - 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 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. + private func avatarFor(_ article: Article) -> NSImage? { + if !showAvatars { + return nil + } -// if let url = article.imageURL { -// if let imageData = appDelegate.imageDownloader.image(for: url) { -// return NSImage(data: imageData) -// } -// } + if let authors = article.authors { + for author in authors { + if let image = avatarForAuthor(author) { + return image + } + } + } - return nil + guard let feed = article.feed else { + return nil + } + + if let feedIcon = appDelegate.feedIconDownloader.icon(for: feed) { + return feedIcon + } + + if let favicon = appDelegate.faviconDownloader.faviconAsAvatar(for: feed) { + return favicon + } + return FaviconGenerator.favicon(feed) + } + + private func avatarForAuthor(_ author: Author) -> NSImage? { + return appDelegate.authorAvatarDownloader.image(for: author) } private func makeTimelineCellEmpty(_ cell: TimelineTableCellView) { @@ -818,7 +753,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 } @@ -831,7 +765,6 @@ private extension TimelineViewController { } @objc func reloadAvailableCells() { - if let indexesToReload = tableView.indexesOfAvailableRows() { reloadCells(for: indexesToReload) } @@ -848,17 +781,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 @@ -892,12 +822,10 @@ private extension TimelineViewController { } func selectedArticleIDs() -> [String] { - return selectedArticles.articleIDs() } func restoreSelection(_ articleIDs: [String]) { - selectArticles(articleIDs) if tableView.selectedRow != -1 { tableView.scrollRowToVisible(tableView.selectedRow) @@ -905,7 +833,6 @@ private extension TimelineViewController { } func performBlockAndRestoreSelection(_ block: (() -> Void)) { - let savedSelection = selectedArticleIDs() block() restoreSelection(savedSelection) @@ -937,7 +864,6 @@ private extension TimelineViewController { } func indexesForArticleIDs(_ articleIDs: Set) -> IndexSet { - var indexes = IndexSet() articleIDs.forEach { (articleID) in @@ -952,7 +878,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. @@ -1020,7 +957,6 @@ private extension TimelineViewController { } func selectArticles(_ articleIDs: [String]) { - let indexesToSelect = indexesForArticleIDs(Set(articleIDs)) if indexesToSelect.isEmpty { tableView.deselectAll(self) @@ -1030,12 +966,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 } @@ -1069,7 +1003,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 { 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 +} diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 868c4efbe..7467594d5 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -241,6 +241,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 */; }; @@ -917,6 +918,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 = ""; }; @@ -1623,6 +1625,7 @@ 84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */, 849A97711ED9EC04007D329B /* TimelineCellData.swift */, 849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */, + 847CD6C9232F4CBF00FAC46D /* TimelineAvatarView.swift */, ); path = Cell; sourceTree = ""; @@ -2188,12 +2191,12 @@ ProvisioningStyle = Automatic; }; 6581C73220CED60000F4AD34 = { - DevelopmentTeam = SHJK2V3AJG; - ProvisioningStyle = Automatic; + DevelopmentTeam = M8L2WTLA8W; + ProvisioningStyle = Manual; }; 840D617B2029031C009BC708 = { CreatedOnToolsVersion = 9.3; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = M8L2WTLA8W; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { @@ -2203,8 +2206,8 @@ }; 849C645F1ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = SHJK2V3AJG; - ProvisioningStyle = Automatic; + DevelopmentTeam = M8L2WTLA8W; + ProvisioningStyle = Manual; SystemCapabilities = { com.apple.HardenedRuntime = { enabled = 1; @@ -2213,7 +2216,7 @@ }; 849C64701ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = SHJK2V3AJG; + DevelopmentTeam = 9C84TZ7Q6Z; ProvisioningStyle = Automatic; TestTargetID = 849C645F1ED37A5D003D8FC0; }; @@ -2724,6 +2727,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 44b1e7fb6..337f09fa6 100644 --- a/Shared/Extensions/RSImage-Extensions.swift +++ b/Shared/Extensions/RSImage-Extensions.swift @@ -27,64 +27,12 @@ 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.avatarBackgroundColor.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 - }