From 1368f3dace7e662270dfa4212b9d350a492ee116 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Mon, 15 Apr 2024 22:21:17 -0700 Subject: [PATCH] Move images-related code into Images module. --- .../xcshareddata/xcschemes/Images.xcscheme | 67 ++++++++++ Images/Package.swift | 9 +- .../Images/AuthorAvatarDownloader.swift | 14 +- .../Images/{ => Favicons}/ColorHash.swift | 0 .../Images}/Favicons/FaviconDownloader.swift | 26 ++-- .../Images}/Favicons/FaviconGenerator.swift | 13 +- .../Images}/Favicons/FaviconURLFinder.swift | 4 +- .../Favicons/SingleFaviconDownloader.swift | 0 .../Images/FeaturedImageDownloader.swift | 100 ++++++++++++++ .../Sources}/Images/FeedIconDownloader.swift | 33 +++-- .../Sources/Images}/IconImage.swift | 20 +-- .../Sources}/Images/ImageDownloader.swift | 12 +- .../Images/RSHTMLMetadata+Extension.swift | 0 .../Sources/Images}/RSImage-Extensions.swift | 0 Mac/AppAssets.swift | 1 + Mac/AppDelegate.swift | 9 +- Mac/MainWindow/IconView.swift | 1 + Mac/MainWindow/Sidebar/Cell/SidebarCell.swift | 1 + .../Sidebar/SidebarViewController.swift | 3 +- .../Timeline/Cell/TimelineCellData.swift | 1 + .../Timeline/TimelineViewController.swift | 3 +- NetNewsWire.xcodeproj/project.pbxproj | 122 ++++-------------- Shared/Activity/ActivityManager.swift | 3 +- Shared/AppDelegate+Shared.swift | 23 ++++ Shared/Extensions/ArticleUtilities.swift | 1 + Shared/Extensions/RSImage-AppIcons.swift | 1 + Shared/Extensions/SmallIconProvider.swift | 1 + Shared/IconImageCache.swift | 1 + Shared/Images/FeaturedImageDownloader.swift | 99 -------------- Shared/SmartFeeds/SearchFeedDelegate.swift | 1 + .../SearchTimelineFeedDelegate.swift | 1 + Shared/SmartFeeds/SmartFeed.swift | 1 + Shared/SmartFeeds/StarredFeedDelegate.swift | 3 +- Shared/SmartFeeds/TodayFeedDelegate.swift | 1 + Shared/SmartFeeds/UnreadFeed.swift | 1 + iOS/AppAssets.swift | 1 + iOS/AppDefaults.swift | 1 + iOS/AppDelegate.swift | 7 +- iOS/Article/ArticleIconSchemeHandler.swift | 1 + iOS/Article/WebViewController.swift | 1 + iOS/Feeds/Cell/FeedTableViewCell.swift | 1 + iOS/Feeds/SidebarViewController.swift | 3 +- iOS/IconView.swift | 1 + .../FeedInspectorViewController.swift | 1 + iOS/SceneCoordinator.swift | 1 + .../TimelineCustomizerViewController.swift | 1 + .../TimelinePreviewTableViewController.swift | 1 + iOS/Timeline/Cell/TimelineCellData.swift | 1 + iOS/Timeline/Cell/TimelineCellLayout.swift | 1 + iOS/Timeline/Cell/TimelineTableViewCell.swift | 1 + iOS/Timeline/TimelineViewController.swift | 3 +- 51 files changed, 343 insertions(+), 259 deletions(-) create mode 100644 Images/.swiftpm/xcode/xcshareddata/xcschemes/Images.xcscheme rename {Shared => Images/Sources}/Images/AuthorAvatarDownloader.swift (82%) rename Images/Sources/Images/{ => Favicons}/ColorHash.swift (100%) rename {Shared => Images/Sources/Images}/Favicons/FaviconDownloader.swift (94%) rename {Shared => Images/Sources/Images}/Favicons/FaviconGenerator.swift (62%) rename {Shared => Images/Sources/Images}/Favicons/FaviconURLFinder.swift (87%) rename {Shared => Images/Sources/Images}/Favicons/SingleFaviconDownloader.swift (100%) create mode 100644 Images/Sources/Images/FeaturedImageDownloader.swift rename {Shared => Images/Sources}/Images/FeedIconDownloader.swift (90%) rename {Shared/Extensions => Images/Sources/Images}/IconImage.swift (92%) rename {Shared => Images/Sources}/Images/ImageDownloader.swift (92%) rename {Shared => Images/Sources}/Images/RSHTMLMetadata+Extension.swift (100%) rename {Shared/Extensions => Images/Sources/Images}/RSImage-Extensions.swift (100%) create mode 100644 Shared/AppDelegate+Shared.swift delete mode 100644 Shared/Images/FeaturedImageDownloader.swift diff --git a/Images/.swiftpm/xcode/xcshareddata/xcschemes/Images.xcscheme b/Images/.swiftpm/xcode/xcshareddata/xcschemes/Images.xcscheme new file mode 100644 index 000000000..131c9d5f2 --- /dev/null +++ b/Images/.swiftpm/xcode/xcshareddata/xcschemes/Images.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Images/Package.swift b/Images/Package.swift index 074499dc3..bdada55ac 100644 --- a/Images/Package.swift +++ b/Images/Package.swift @@ -10,11 +10,18 @@ let package = Package( name: "Images", targets: ["Images"]), ], + dependencies: [ + .package(path: "../Core"), + .package(path: "../Articles"), + .package(path: "../Account") + ], targets: [ .target( name: "Images", dependencies: [ - + "Core", + "Articles", + "Account" ], swiftSettings: [ .enableExperimentalFeature("StrictConcurrency") diff --git a/Shared/Images/AuthorAvatarDownloader.swift b/Images/Sources/Images/AuthorAvatarDownloader.swift similarity index 82% rename from Shared/Images/AuthorAvatarDownloader.swift rename to Images/Sources/Images/AuthorAvatarDownloader.swift index ecf95c671..9d105ee7d 100644 --- a/Shared/Images/AuthorAvatarDownloader.swift +++ b/Images/Sources/Images/AuthorAvatarDownloader.swift @@ -10,28 +10,28 @@ import Foundation import Articles import Core -extension Notification.Name { +public extension Notification.Name { static let AvatarDidBecomeAvailable = Notification.Name("AvatarDidBecomeAvailableNotification") // UserInfoKey.imageURL (which is an avatarURL) } -@MainActor final class AuthorAvatarDownloader { +@MainActor public final class AuthorAvatarDownloader { private let imageDownloader: ImageDownloader private var cache = [String: IconImage]() // avatarURL: RSImage private var waitingForAvatarURLs = Set() - init(imageDownloader: ImageDownloader) { + public init(imageDownloader: ImageDownloader) { self.imageDownloader = imageDownloader NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .ImageDidBecomeAvailable, object: imageDownloader) } - func resetCache() { + public func resetCache() { cache = [String: IconImage]() } - func image(for author: Author) -> IconImage? { + public func image(for author: Author) -> IconImage? { guard let avatarURL = author.avatarURL else { return nil @@ -52,7 +52,7 @@ extension Notification.Name { } @objc func imageDidBecomeAvailable(_ note: Notification) { - guard let avatarURL = note.userInfo?[UserInfoKey.url] as? String else { + guard let avatarURL = note.userInfo?[ImageDownloader.imageURLKey] as? String else { return } guard waitingForAvatarURLs.contains(avatarURL) else { @@ -88,7 +88,7 @@ private extension AuthorAvatarDownloader { func postAvatarDidBecomeAvailableNotification(_ avatarURL: String) { DispatchQueue.main.async { - NotificationCenter.default.post(name: .AvatarDidBecomeAvailable, object: self, userInfo: [UserInfoKey.url: avatarURL]) + NotificationCenter.default.post(name: .AvatarDidBecomeAvailable, object: self, userInfo: [ImageDownloader.imageURLKey: avatarURL]) } } } diff --git a/Images/Sources/Images/ColorHash.swift b/Images/Sources/Images/Favicons/ColorHash.swift similarity index 100% rename from Images/Sources/Images/ColorHash.swift rename to Images/Sources/Images/Favicons/ColorHash.swift diff --git a/Shared/Favicons/FaviconDownloader.swift b/Images/Sources/Images/Favicons/FaviconDownloader.swift similarity index 94% rename from Shared/Favicons/FaviconDownloader.swift rename to Images/Sources/Images/Favicons/FaviconDownloader.swift index 168cfba58..e24653ab3 100644 --- a/Shared/Favicons/FaviconDownloader.swift +++ b/Images/Sources/Images/Favicons/FaviconDownloader.swift @@ -12,12 +12,20 @@ import Articles import Account import UniformTypeIdentifiers import Core +import ParserObjC -extension Notification.Name { +public extension Notification.Name { static let FaviconDidBecomeAvailable = Notification.Name("FaviconDidBecomeAvailableNotification") // userInfo key: FaviconDownloader.UserInfoKey.faviconURL } -@MainActor final class FaviconDownloader { +public protocol FaviconDownloaderDelegate { + + var appIconImage: IconImage? { get } + + func downloadMetadata(_ url: String) async throws -> RSHTMLMetadata? +} + +@MainActor public final class FaviconDownloader { private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0) @@ -46,11 +54,13 @@ extension Notification.Name { private let queue: DispatchQueue private var cache = [Feed: IconImage]() // faviconURL: RSImage + public var delegate: FaviconDownloaderDelegate? + struct UserInfoKey { static let faviconURL = "faviconURL" } - init(folder: String) { + public init(folder: String) { self.folder = folder self.diskCache = BinaryDiskCache(folder: folder) @@ -70,7 +80,7 @@ extension Notification.Name { cache = [Feed: IconImage]() } - func favicon(for feed: Feed) -> IconImage? { + public func favicon(for feed: Feed) -> IconImage? { assert(Thread.isMainThread) @@ -92,8 +102,8 @@ extension Notification.Name { return nil } - func faviconAsIcon(for feed: Feed) -> IconImage? { - + public func faviconAsIcon(for feed: Feed) -> IconImage? { + if let image = cache[feed] { return image } @@ -120,7 +130,7 @@ extension Notification.Name { if let url = URL(string: homePageURL) { if url.host == "nnw.ranchero.com" || url.host == "netnewswire.blog" { - return IconImage.appIcon + return delegate?.appIconImage } } @@ -203,7 +213,7 @@ private extension FaviconDownloader { guard let url = URL(unicodeString: homePageURL) else { return nil } - guard let faviconURLs = await FaviconURLFinder.findFaviconURLs(with: homePageURL) else { + guard let faviconURLs = await FaviconURLFinder.findFaviconURLs(with: homePageURL, downloadMetadata: delegate!.downloadMetadata(_:)) else { return nil } diff --git a/Shared/Favicons/FaviconGenerator.swift b/Images/Sources/Images/Favicons/FaviconGenerator.swift similarity index 62% rename from Shared/Favicons/FaviconGenerator.swift rename to Images/Sources/Images/Favicons/FaviconGenerator.swift index ab4fece88..40f2f6657 100644 --- a/Shared/Favicons/FaviconGenerator.swift +++ b/Images/Sources/Images/Favicons/FaviconGenerator.swift @@ -8,27 +8,26 @@ import Foundation import Account -import Images +import Core -@MainActor final class FaviconGenerator { +@MainActor public final class FaviconGenerator { private static var faviconGeneratorCache = [String: IconImage]() // feedURL: RSImage + public static var faviconTemplateImage: RSImage! // Must be set at startup - static func favicon(_ feed: Feed) -> IconImage { + public static func favicon(_ feed: Feed) -> IconImage { if let favicon = FaviconGenerator.faviconGeneratorCache[feed.url] { return favicon } let colorHash = ColorHash(feed.url) - if let favicon = AppAssets.faviconTemplateImage.maskWithColor(color: colorHash.color.cgColor) { + if let favicon = faviconTemplateImage.maskWithColor(color: colorHash.color.cgColor) { let iconImage = IconImage(favicon, isBackgroundSupressed: true) FaviconGenerator.faviconGeneratorCache[feed.url] = iconImage return iconImage } else { - return IconImage(AppAssets.faviconTemplateImage, isBackgroundSupressed: true) + return IconImage(faviconTemplateImage, isBackgroundSupressed: true) } - } - } diff --git a/Shared/Favicons/FaviconURLFinder.swift b/Images/Sources/Images/Favicons/FaviconURLFinder.swift similarity index 87% rename from Shared/Favicons/FaviconURLFinder.swift rename to Images/Sources/Images/Favicons/FaviconURLFinder.swift index f634c9364..5e704fe8a 100644 --- a/Shared/Favicons/FaviconURLFinder.swift +++ b/Images/Sources/Images/Favicons/FaviconURLFinder.swift @@ -22,14 +22,14 @@ import UniformTypeIdentifiers /// - Parameters: /// - homePageURL: The page to search. /// - urls: An array of favicon URLs as strings. - static func findFaviconURLs(with homePageURL: String) async -> [String]? { + static func findFaviconURLs(with homePageURL: String, downloadMetadata: ((String) async throws -> RSHTMLMetadata?)) async -> [String]? { guard let _ = URL(unicodeString: homePageURL) else { return nil } // If the favicon has an explicit type, check that for an ignored type; otherwise, check the file extension. - let htmlMetadata = try? await HTMLMetadataDownloader.downloadMetadata(for: homePageURL) + let htmlMetadata = try? await downloadMetadata(homePageURL) let faviconURLs = htmlMetadata?.favicons.compactMap { favicon -> String? in shouldAllowFavicon(favicon) ? favicon.urlString : nil diff --git a/Shared/Favicons/SingleFaviconDownloader.swift b/Images/Sources/Images/Favicons/SingleFaviconDownloader.swift similarity index 100% rename from Shared/Favicons/SingleFaviconDownloader.swift rename to Images/Sources/Images/Favicons/SingleFaviconDownloader.swift diff --git a/Images/Sources/Images/FeaturedImageDownloader.swift b/Images/Sources/Images/FeaturedImageDownloader.swift new file mode 100644 index 000000000..1746cde36 --- /dev/null +++ b/Images/Sources/Images/FeaturedImageDownloader.swift @@ -0,0 +1,100 @@ +//// +//// FeaturedImageDownloader.swift +//// NetNewsWire +//// +//// Created by Brent Simmons on 11/26/17. +//// Copyright © 2017 Ranchero Software. All rights reserved. +//// +// +//import Foundation +//import Articles +//import Parser +//import Core +// +//final class FeaturedImageDownloader { +// +// private let imageDownloader: ImageDownloader +// private var articleURLToFeaturedImageURLCache = [String: String]() +// private var articleURLsWithNoFeaturedImage = Set() +// private var urlsInProgress = Set() +// +// init(imageDownloader: ImageDownloader) { +// +// self.imageDownloader = imageDownloader +// } +// +// func image(for article: Article) -> RSImage? { +// +// if let imageLink = article.imageLink { +// return image(forFeaturedImageURL: imageLink) +// } +// if let link = article.link { +// return image(forArticleURL: link) +// } +// return nil +// } +// +// func image(forArticleURL articleURL: String) -> RSImage? { +// +// if articleURLsWithNoFeaturedImage.contains(articleURL) { +// return nil +// } +// +// if let featuredImageURL = cachedURL(for: articleURL) { +// return image(forFeaturedImageURL: featuredImageURL) +// } +// findFeaturedImageURL(for: articleURL) +// return nil +// } +// +// func image(forFeaturedImageURL featuredImageURL: String) -> RSImage? { +// if let data = imageDownloader.image(for: featuredImageURL) { +// return RSImage(data: data) +// } +// return nil +// } +// +//} +// +//private extension FeaturedImageDownloader { +// +// func cachedURL(for articleURL: String) -> String? { +// +// return articleURLToFeaturedImageURLCache[articleURL] +// } +// +// func cacheURL(for articleURL: String, _ featuredImageURL: String) { +// +// articleURLsWithNoFeaturedImage.remove(articleURL) +// articleURLToFeaturedImageURLCache[articleURL] = featuredImageURL +// } +// +// func findFeaturedImageURL(for articleURL: String) { +// +// guard !urlsInProgress.contains(articleURL) else { +// return +// } +// urlsInProgress.insert(articleURL) +// +// HTMLMetadataDownloader.downloadMetadata(for: articleURL) { (metadata) in +// +// self.urlsInProgress.remove(articleURL) +// +// guard let metadata = metadata else { +// return +// } +// self.pullFeaturedImageURL(from: metadata, articleURL: articleURL) +// } +// } +// +// func pullFeaturedImageURL(from metadata: RSHTMLMetadata, articleURL: String) { +// +// if let url = metadata.bestFeaturedImageURL() { +// cacheURL(for: articleURL, url) +// let _ = image(forFeaturedImageURL: url) +// return +// } +// +// articleURLsWithNoFeaturedImage.insert(articleURL) +// } +//} diff --git a/Shared/Images/FeedIconDownloader.swift b/Images/Sources/Images/FeedIconDownloader.swift similarity index 90% rename from Shared/Images/FeedIconDownloader.swift rename to Images/Sources/Images/FeedIconDownloader.swift index e8212198e..7d49bb26f 100644 --- a/Shared/Images/FeedIconDownloader.swift +++ b/Images/Sources/Images/FeedIconDownloader.swift @@ -13,13 +13,22 @@ import Web import Parser import Core -extension Notification.Name { +public extension Notification.Name { static let FeedIconDidBecomeAvailable = Notification.Name("FeedIconDidBecomeAvailableNotification") // UserInfoKey.feed } +public protocol FeedIconDownloaderDelegate { + + var appIconImage: IconImage? { get } + + func downloadMetadata(_ url: String) async throws -> RSHTMLMetadata? +} + @MainActor public final class FeedIconDownloader { + public static let feedKey = "url" + private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0) private let imageDownloader: ImageDownloader @@ -56,7 +65,9 @@ extension Notification.Name { private var cache = [Feed: IconImage]() private var waitingForFeedURLs = [String: Feed]() - init(imageDownloader: ImageDownloader, folder: String) { + public var delegate: FeedIconDownloaderDelegate? + + public init(imageDownloader: ImageDownloader, folder: String) { self.imageDownloader = imageDownloader self.feedURLToIconURLCachePath = (folder as NSString).appendingPathComponent("FeedURLToIconURLCache.plist") self.homePageToIconURLCachePath = (folder as NSString).appendingPathComponent("HomePageToIconURLCache.plist") @@ -71,14 +82,14 @@ extension Notification.Name { cache = [Feed: IconImage]() } - func icon(for feed: Feed) -> IconImage? { + public func icon(for feed: Feed) -> IconImage? { if let cachedImage = cache[feed] { return cachedImage } if let hpURLString = feed.homePageURL, let hpURL = URL(string: hpURLString), (hpURL.host == "nnw.ranchero.com" || hpURL.host == "netnewswire.blog") { - return IconImage.appIcon + return delegate?.appIconImage } @MainActor func checkHomePageURL() { @@ -87,7 +98,7 @@ extension Notification.Name { } icon(forHomePageURL: homePageURL, feed: feed) { (image) in Task { @MainActor in - if let image = image { + if let image { self.postFeedIconDidBecomeAvailableNotification(feed) self.cache[feed] = IconImage(image) } @@ -99,7 +110,7 @@ extension Notification.Name { if let iconURL = feed.iconURL { icon(forURL: iconURL, feed: feed) { (image) in Task { @MainActor in - if let image = image { + if let image { self.postFeedIconDidBecomeAvailableNotification(feed) self.cache[feed] = IconImage(image) } else { @@ -130,7 +141,7 @@ extension Notification.Name { } @objc func imageDidBecomeAvailable(_ note: Notification) { - guard let url = note.userInfo?[UserInfoKey.url] as? String, let feed = waitingForFeedURLs[url] else { + guard let url = note.userInfo?[ImageDownloader.imageURLKey] as? String, let feed = waitingForFeedURLs[url] else { return } waitingForFeedURLs[url] = nil @@ -171,7 +182,7 @@ private extension FeedIconDownloader { return } - findIconURLForHomePageURL(homePageURL, feed: feed) + findIconURLForHomePageURL(homePageURL, feed: feed, downloadMetadata: delegate!.downloadMetadata(_:)) } func icon(forURL url: String, feed: Feed, _ imageResultBlock: @Sendable @escaping (RSImage?) -> Void) { @@ -190,7 +201,7 @@ private extension FeedIconDownloader { func postFeedIconDidBecomeAvailableNotification(_ feed: Feed) { DispatchQueue.main.async { - let userInfo: [AnyHashable: Any] = [UserInfoKey.feed: feed] + let userInfo: [AnyHashable: Any] = [Self.feedKey: feed] NotificationCenter.default.post(name: .FeedIconDidBecomeAvailable, object: self, userInfo: userInfo) } } @@ -207,7 +218,7 @@ private extension FeedIconDownloader { homePageToIconURLCacheDirty = true } - func findIconURLForHomePageURL(_ homePageURL: String, feed: Feed) { + func findIconURLForHomePageURL(_ homePageURL: String, feed: Feed, downloadMetadata: @escaping (String) async throws -> RSHTMLMetadata?) { guard !urlsInProgress.contains(homePageURL) else { return @@ -216,7 +227,7 @@ private extension FeedIconDownloader { Task { @MainActor in - let metadata = try? await HTMLMetadataDownloader.downloadMetadata(for: homePageURL) + let metadata = try? await downloadMetadata(homePageURL) self.urlsInProgress.remove(homePageURL) guard let metadata else { diff --git a/Shared/Extensions/IconImage.swift b/Images/Sources/Images/IconImage.swift similarity index 92% rename from Shared/Extensions/IconImage.swift rename to Images/Sources/Images/IconImage.swift index 654985fb0..a9bad4664 100644 --- a/Shared/Extensions/IconImage.swift +++ b/Images/Sources/Images/IconImage.swift @@ -13,22 +13,22 @@ import UIKit #endif import Core -final class IconImage { +public final class IconImage { - lazy var isDark: Bool = { + public lazy var isDark: Bool = { return image.isDark() }() - lazy var isBright: Bool = { + public lazy var isBright: Bool = { return image.isBright() }() - let image: RSImage - let isSymbol: Bool - let isBackgroundSupressed: Bool - let preferredColor: CGColor? + public let image: RSImage + public let isSymbol: Bool + public let isBackgroundSupressed: Bool + public let preferredColor: CGColor? - init(_ image: RSImage, isSymbol: Bool = false, isBackgroundSupressed: Bool = false, preferredColor: CGColor? = nil) { + public init(_ image: RSImage, isSymbol: Bool = false, isBackgroundSupressed: Bool = false, preferredColor: CGColor? = nil) { self.image = image self.isSymbol = isSymbol self.preferredColor = preferredColor @@ -161,7 +161,7 @@ extension CGImage { } -enum IconSize: Int, CaseIterable { +public enum IconSize: Int, CaseIterable { case small = 1 case medium = 2 case large = 3 @@ -170,7 +170,7 @@ enum IconSize: Int, CaseIterable { private static let mediumDimension = CGFloat(integerLiteral: 36) private static let largeDimension = CGFloat(integerLiteral: 48) - var size: CGSize { + public var size: CGSize { switch self { case .small: return CGSize(width: IconSize.smallDimension, height: IconSize.smallDimension) diff --git a/Shared/Images/ImageDownloader.swift b/Images/Sources/Images/ImageDownloader.swift similarity index 92% rename from Shared/Images/ImageDownloader.swift rename to Images/Sources/Images/ImageDownloader.swift index 2031e4a01..3c301a7f6 100644 --- a/Shared/Images/ImageDownloader.swift +++ b/Images/Sources/Images/ImageDownloader.swift @@ -12,12 +12,14 @@ import Web import FoundationExtras import Core -extension Notification.Name { +public extension Notification.Name { static let ImageDidBecomeAvailable = Notification.Name("ImageDidBecomeAvailableNotification") // UserInfoKey.url } -final class ImageDownloader { +public final class ImageDownloader { + + public static let imageURLKey = "url" private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "ImageDownloader") @@ -28,7 +30,7 @@ final class ImageDownloader { private var urlsInProgress = Set() private var badURLs = Set() // That return a 404 or whatever. Just skip them in the future. - init(folder: String) { + public init(folder: String) { self.folder = folder self.diskCache = BinaryDiskCache(folder: folder) @@ -36,7 +38,7 @@ final class ImageDownloader { } @discardableResult - func image(for url: String) -> Data? { + public func image(for url: String) -> Data? { if let data = imageCache[url] { return data @@ -140,7 +142,7 @@ private extension ImageDownloader { func postImageDidBecomeAvailableNotification(_ url: String) { DispatchQueue.main.async { - NotificationCenter.default.post(name: .ImageDidBecomeAvailable, object: self, userInfo: [UserInfoKey.url: url]) + NotificationCenter.default.post(name: .ImageDidBecomeAvailable, object: self, userInfo: [Self.imageURLKey: url]) } } } diff --git a/Shared/Images/RSHTMLMetadata+Extension.swift b/Images/Sources/Images/RSHTMLMetadata+Extension.swift similarity index 100% rename from Shared/Images/RSHTMLMetadata+Extension.swift rename to Images/Sources/Images/RSHTMLMetadata+Extension.swift diff --git a/Shared/Extensions/RSImage-Extensions.swift b/Images/Sources/Images/RSImage-Extensions.swift similarity index 100% rename from Shared/Extensions/RSImage-Extensions.swift rename to Images/Sources/Images/RSImage-Extensions.swift diff --git a/Mac/AppAssets.swift b/Mac/AppAssets.swift index f445b5692..db8368dd3 100644 --- a/Mac/AppAssets.swift +++ b/Mac/AppAssets.swift @@ -8,6 +8,7 @@ import AppKit import Account +import Images struct AppAssets { diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 7c6df05d9..462d0fc02 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -17,6 +17,7 @@ import Secrets import OSLog import Core import CrashReporter +import Images // If we're not going to import Sparkle, provide dummy protocols to make it easy // for AppDelegate to comply @@ -170,7 +171,9 @@ import Sparkle let faviconsFolder = (cacheFolder as NSString).appendingPathComponent("Favicons") let faviconsFolderURL = URL(fileURLWithPath: faviconsFolder) try! FileManager.default.createDirectory(at: faviconsFolderURL, withIntermediateDirectories: true, attributes: nil) + faviconDownloader = FaviconDownloader(folder: faviconsFolder) + faviconDownloader.delegate = self let imagesFolder = (cacheFolder as NSString).appendingPathComponent("Images") let imagesFolderURL = URL(fileURLWithPath: imagesFolder) @@ -179,7 +182,8 @@ import Sparkle authorAvatarDownloader = AuthorAvatarDownloader(imageDownloader: imageDownloader) feedIconDownloader = FeedIconDownloader(imageDownloader: imageDownloader, folder: cacheFolder) - + feedIconDownloader.delegate = self + appName = (Bundle.main.infoDictionary!["CFBundleExecutable"]! as! String) } @@ -206,6 +210,9 @@ import Sparkle if isFirstRun { os_log(.debug, "Is first run.") } + + FaviconGenerator.faviconTemplateImage = AppAssets.faviconTemplateImage + let localAccount = accountManager.defaultAccount if isFirstRun && !accountManager.anyAccountHasAtLeastOneFeed() { diff --git a/Mac/MainWindow/IconView.swift b/Mac/MainWindow/IconView.swift index 43e4c8444..743cf88de 100644 --- a/Mac/MainWindow/IconView.swift +++ b/Mac/MainWindow/IconView.swift @@ -7,6 +7,7 @@ // import AppKit +import Images final class IconView: NSView { diff --git a/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift b/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift index 5c28e3979..7cefbd021 100644 --- a/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift +++ b/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift @@ -9,6 +9,7 @@ import Foundation import Account import Tree +import Images class SidebarCell : NSTableCellView { diff --git a/Mac/MainWindow/Sidebar/SidebarViewController.swift b/Mac/MainWindow/Sidebar/SidebarViewController.swift index 82207815f..ae2040a75 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController.swift @@ -11,6 +11,7 @@ import Tree import Articles import Account import Core +import Images extension Notification.Name { static let appleSideBarDefaultIconSizeChanged = Notification.Name("AppleSideBarDefaultIconSizeChanged") @@ -198,7 +199,7 @@ protocol SidebarDelegate: AnyObject { } @objc func feedIconDidBecomeAvailable(_ note: Notification) { - guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed else { return } + guard let feed = note.userInfo?[FeedIconDownloader.feedKey] as? Feed else { return } configureCellsForRepresentedObject(feed) } diff --git a/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift b/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift index 62634a9f6..81a9c1dfd 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift @@ -8,6 +8,7 @@ import AppKit import Articles +import Images @MainActor struct TimelineCellData { diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index 254d2f4e8..dc6689d95 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -11,6 +11,7 @@ import Articles import Account import os.log import Core +import Images protocol TimelineDelegate: AnyObject { @@ -601,7 +602,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } @objc func feedIconDidBecomeAvailable(_ note: Notification) { - guard showIcons, let feed = note.userInfo?[UserInfoKey.feed] as? Feed else { + guard showIcons, let feed = note.userInfo?[FeedIconDownloader.feedKey] as? Feed else { return } let indexesToReload = tableView.indexesOfAvailableRowsPassingTest { (row) -> Bool in diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index f84af7181..a46cddb31 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -93,7 +93,6 @@ 510FFAB326EEA22C00F32265 /* ArticleThemesTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510FFAB226EEA22C00F32265 /* ArticleThemesTableViewController.swift */; }; 51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */; }; 5110C37D2373A8D100A9C04F /* InspectorIconHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */; }; - 51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; }; 5117715524E1EA0F00A2A836 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5117715424E1EA0F00A2A836 /* ArticleExtractorButton.swift */; }; 5117715624E1EA0F00A2A836 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5117715424E1EA0F00A2A836 /* ArticleExtractorButton.swift */; }; 511B148924E5DBDD00C919BD /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 511B148824E5DBDD00C919BD /* Account */; }; @@ -169,7 +168,6 @@ 5148F4552336DB7000F8CD8B /* TimelineTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5148F4542336DB7000F8CD8B /* TimelineTitleView.swift */; }; 514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */; }; 514C16CE24D2E63F009A3AFA /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 514C16CD24D2E63F009A3AFA /* Account */; }; - 5154368B229404D1005E1CDF /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; }; 515D4FCA23257CB500EE1167 /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; }; 515D4FCC2325815A00EE1167 /* SafariExt.js in Resources */ = {isa = PBXBuildFile; fileRef = 515D4FCB2325815A00EE1167 /* SafariExt.js */; }; 516244E3241E19F000B61C47 /* ColorPaletteTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516244E2241E19F000B61C47 /* ColorPaletteTableViewController.swift */; }; @@ -182,8 +180,6 @@ 516A09402361240900EAE89B /* Account.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 516A093F2361240900EAE89B /* Account.storyboard */; }; 516A09422361248000EAE89B /* Inspector.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 516A09412361248000EAE89B /* Inspector.storyboard */; }; 516AE9B32371C372007DEEAA /* FeedTableViewSectionHeaderLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9B22371C372007DEEAA /* FeedTableViewSectionHeaderLayout.swift */; }; - 516AE9DF2372269A007DEEAA /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; }; - 516AE9E02372269A007DEEAA /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; }; 516B695F24D2F33B00B5702F /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 516B695E24D2F33B00B5702F /* Account */; }; 51707439232AA97100A461A3 /* ShareFolderPickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */; }; 517630042336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; @@ -226,7 +222,6 @@ 51A9A5E82380CA130033AADF /* ShareFolderPickerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A9A5E72380CA130033AADF /* ShareFolderPickerCell.swift */; }; 51A9A5ED2380D6000033AADF /* AppAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45254226507D200C03939 /* AppAssets.swift */; }; 51A9A5EE2380D6080033AADF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC9B2262A1A900D921D6 /* Assets.xcassets */; }; - 51A9A5EF2380D63B0033AADF /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; }; 51A9A5F22380DE520033AADF /* AddFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddFeedDefaultContainer.swift */; }; 51A9A5F32380DE530033AADF /* AddFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddFeedDefaultContainer.swift */; }; 51A9A5F52380F6A60033AADF /* ModalNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A9A5F42380F6A60033AADF /* ModalNavigationController.swift */; }; @@ -284,18 +279,10 @@ 51C45297226509E300C03939 /* DefaultFeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */; }; 51C4529922650A0000C03939 /* ArticleThemesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleThemesManager.swift */; }; 51C4529A22650A0400C03939 /* ArticleTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleTheme.swift */; }; - 51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; }; - 51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; }; - 51C4529D22650A1000C03939 /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; - 51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; }; - 51C4529F22650A1900C03939 /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; }; - 51C452A022650A1900C03939 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; }; - 51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; }; 51C452A322650A1E00C03939 /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; }; 51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */; }; 51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */; }; 51C452A622650A3500C03939 /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; }; - 51C452A722650A3D00C03939 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; }; 51C452A922650DC600C03939 /* ArticleRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */; }; 51C452AB22650DC600C03939 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; }; 51C452AC22650FD200C03939 /* AppNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */; }; @@ -336,7 +323,6 @@ 51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; }; 51EAED96231363EF00A9EEE3 /* NonIntrinsicButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EAED95231363EF00A9EEE3 /* NonIntrinsicButton.swift */; }; 51EC114C2149FE3300B296E3 /* FolderTreeMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */; }; - 51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; }; 51EF0F7E2277A57D0050506E /* TimelineAccessibilityCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F7D2277A57D0050506E /* TimelineAccessibilityCellLayout.swift */; }; 51EF0F802277A8330050506E /* TimelineCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F7F2277A8330050506E /* TimelineCellLayout.swift */; }; 51EFDA1B24E6D16A0085C3D6 /* SafariExt.js in Resources */ = {isa = PBXBuildFile; fileRef = 515D4FCB2325815A00EE1167 /* SafariExt.js */; }; @@ -365,7 +351,6 @@ 653813182680E152007A082C /* AccountType+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173A64162547BE0900267F6E /* AccountType+Helpers.swift */; }; 653813192680E15B007A082C /* CacheCleaner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6B52375E612001ABC45 /* CacheCleaner.swift */; }; 6538131A2680E16C007A082C /* ExportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849C78912362AB04009A71E4 /* ExportOPMLWindowController.swift */; }; - 6538131B2680E176007A082C /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; }; 6538131C2680E17F007A082C /* UserInfoKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9805237DCAC90028BCAA /* UserInfoKey.swift */; }; 6538131E2680E1CA007A082C /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 6538131D2680E1CA007A082C /* Account */; }; 6538131F2680E1CA007A082C /* Account in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 6538131D2680E1CA007A082C /* Account */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; @@ -432,7 +417,6 @@ 65ED3FDC235DEF6C0081F399 /* UnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97631ED9EB96007D329B /* UnreadCountView.swift */; }; 65ED3FDD235DEF6C0081F399 /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; }; 65ED3FDE235DEF6C0081F399 /* CrashReportWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840BEE4021D70E64009BBAFA /* CrashReportWindowController.swift */; }; - 65ED3FDF235DEF6C0081F399 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; }; 65ED3FE0235DEF6C0081F399 /* PreferencesControlsBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC7122629E1200D921D6 /* PreferencesControlsBackgroundView.swift */; }; 65ED3FE1235DEF6C0081F399 /* MarkCommandValidationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84162A142038C12C00035290 /* MarkCommandValidationStatus.swift */; }; 65ED3FE2235DEF6C0081F399 /* ArticlePasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */; }; @@ -442,9 +426,7 @@ 65ED3FE6235DEF6C0081F399 /* RenameWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A37CB4201ECD610087C5AF /* RenameWindowController.swift */; }; 65ED3FE7235DEF6C0081F399 /* SendToMicroBlogCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */; }; 65ED3FE8235DEF6C0081F399 /* ArticleTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleTheme.swift */; }; - 65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; 65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B7178B201E66580091657D /* SidebarViewController+ContextualMenus.swift */; }; - 65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; }; 65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */; }; 65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; }; 65ED3FEF235DEF6C0081F399 /* ScriptingObjectContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5907DB12004BB37005947E5 /* ScriptingObjectContainer.swift */; }; @@ -466,7 +448,6 @@ 65ED3FFF235DEF6C0081F399 /* SidebarOutlineDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EBB2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift */; }; 65ED4000235DEF6C0081F399 /* SidebarCellAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29231FC9255E007B49E3 /* SidebarCellAppearance.swift */; }; 65ED4001235DEF6C0081F399 /* StarredFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */; }; - 65ED4002235DEF6C0081F399 /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; }; 65ED4003235DEF6C0081F399 /* AdvancedPreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC6B22629E1200D921D6 /* AdvancedPreferencesViewController.swift */; }; 65ED4004235DEF6C0081F399 /* SharingServicePickerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849EE72020391F560082A1EA /* SharingServicePickerDelegate.swift */; }; 65ED4005235DEF6C0081F399 /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; }; @@ -478,7 +459,6 @@ 65ED400B235DEF6C0081F399 /* TodayFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5361FC22FCB00998D64 /* TodayFeedDelegate.swift */; }; 65ED400C235DEF6C0081F399 /* FolderInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */; }; 65ED400D235DEF6C0081F399 /* SmartFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DEE56422C32CA4005FC42C /* SmartFeedDelegate.swift */; }; - 65ED400E235DEF6C0081F399 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; }; 65ED400F235DEF6C0081F399 /* LegacyArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73B62332D5F70090D516 /* LegacyArticleExtractorButton.swift */; }; 65ED4011235DEF6C0081F399 /* AddFolderWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */; }; 65ED4012235DEF6C0081F399 /* TimelineContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DDA422168C62008CE1BF /* TimelineContainerViewController.swift */; }; @@ -491,7 +471,6 @@ 65ED4019235DEF6C0081F399 /* FetchRequestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAFCAE22BC8C35007694F0 /* FetchRequestOperation.swift */; }; 65ED401A235DEF6C0081F399 /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; }; 65ED401B235DEF6C0081F399 /* TimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A976B1ED9EBC8007D329B /* TimelineViewController.swift */; }; - 65ED401C235DEF6C0081F399 /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; }; 65ED401D235DEF6C0081F399 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; 65ED401E235DEF6C0081F399 /* TimelineCellData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97711ED9EC04007D329B /* TimelineCellData.swift */; }; 65ED401F235DEF6C0081F399 /* BuiltinSmartFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */; }; @@ -517,10 +496,7 @@ 65ED4035235DEF6C0081F399 /* FolderTreeMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */; }; 65ED4036235DEF6C0081F399 /* NNW3ImportController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849ADEE02359817D000E1B81 /* NNW3ImportController.swift */; }; 65ED4037235DEF6C0081F399 /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; }; - 65ED4038235DEF6C0081F399 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; }; - 65ED4039235DEF6C0081F399 /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; }; 65ED403A235DEF6C0081F399 /* Feed+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB620074D6500B9E363 /* Feed+Scriptability.swift */; }; - 65ED403B235DEF6C0081F399 /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; }; 65ED403C235DEF6C0081F399 /* SingleLineTextFieldSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */; }; 65ED403D235DEF6C0081F399 /* TimelineTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97741ED9EC04007D329B /* TimelineTableCellView.swift */; }; 65ED403E235DEF6C0081F399 /* TimelineCellAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97701ED9EC04007D329B /* TimelineCellAppearance.swift */; }; @@ -581,9 +557,7 @@ 841CECDC2BAD04BF0001EE72 /* Tree in Frameworks */ = {isa = PBXBuildFile; productRef = 841CECDB2BAD04BF0001EE72 /* Tree */; }; 841CECDE2BAD06D10001EE72 /* Tree in Frameworks */ = {isa = PBXBuildFile; productRef = 841CECDD2BAD06D10001EE72 /* Tree */; }; 84216D0322128B9D0049B9B9 /* DetailWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */; }; - 8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; }; 8426119E1FCB6ED40086A189 /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; }; - 842611A21FCB769D0086A189 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; }; 842E45CE1ED8C308000A8B52 /* AppNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */; }; 842E45DD1ED8C54B000A8B52 /* Browser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45DC1ED8C54B000A8B52 /* Browser.swift */; }; 8438C2DB2BABE0B00040C9EE /* CoreResources in Frameworks */ = {isa = PBXBuildFile; productRef = 8438C2DA2BABE0B00040C9EE /* CoreResources */; }; @@ -603,7 +577,6 @@ 845122722B8CEA9100480DB0 /* SidebarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8451226E2B8CEA9100480DB0 /* SidebarItem.swift */; }; 845122732B8CEA9100480DB0 /* SidebarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8451226E2B8CEA9100480DB0 /* SidebarItem.swift */; }; 845122742B8CEA9100480DB0 /* SidebarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8451226E2B8CEA9100480DB0 /* SidebarItem.swift */; }; - 845213231FCA5B11003B6E93 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; }; 845479881FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 845479871FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist */; }; 8454C3F3263F2D8700E3F9C7 /* IconImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8454C3F2263F2D8700E3F9C7 /* IconImageCache.swift */; }; 8454C3F8263F3AD400E3F9C7 /* IconImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8454C3F2263F2D8700E3F9C7 /* IconImageCache.swift */; }; @@ -616,7 +589,6 @@ 845611722BBD145D00507B73 /* Parser in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 845611702BBD145D00507B73 /* Parser */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 845611742BBD145D00507B73 /* ParserObjC in Frameworks */ = {isa = PBXBuildFile; productRef = 845611732BBD145D00507B73 /* ParserObjC */; }; 845611752BBD145D00507B73 /* ParserObjC in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 845611732BBD145D00507B73 /* ParserObjC */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 845A29091FC74B8E007B49E3 /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; }; 845A29221FC9251E007B49E3 /* SidebarCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29211FC9251E007B49E3 /* SidebarCellLayout.swift */; }; 845A29241FC9255E007B49E3 /* SidebarCellAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29231FC9255E007B49E3 /* SidebarCellAppearance.swift */; }; 845EE7B11FC2366500854A1F /* StarredFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */; }; @@ -640,7 +612,6 @@ 848565512B9E910200F4BAE0 /* FMDB in Frameworks */ = {isa = PBXBuildFile; productRef = 848565502B9E910200F4BAE0 /* FMDB */; }; 848B937221C8C5540038DC0D /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848B937121C8C5540038DC0D /* CrashReporter.swift */; }; 848D578E21543519005FFAD5 /* PasteboardFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardFeed.swift */; }; - 848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; }; 849830252BBBA6130024FB5B /* Web in Frameworks */ = {isa = PBXBuildFile; productRef = 849830242BBBA6130024FB5B /* Web */; }; 849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */; }; 849A97531ED9EAC0007D329B /* AddFeedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97511ED9EAC0007D329B /* AddFeedController.swift */; }; @@ -717,6 +688,11 @@ 84D9582C2BABE53B0053E7B2 /* FoundationExtras in Frameworks */ = {isa = PBXBuildFile; productRef = 84D9582B2BABE53B0053E7B2 /* FoundationExtras */; }; 84DC5FFA2BCE31D200F04682 /* Images in Frameworks */ = {isa = PBXBuildFile; productRef = 84DC5FF92BCE31D200F04682 /* Images */; }; 84DC5FFC2BCE31DB00F04682 /* Images in Frameworks */ = {isa = PBXBuildFile; productRef = 84DC5FFB2BCE31DB00F04682 /* Images */; }; + 84DC5FFE2BCE37A300F04682 /* AppDelegate+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DC5FFD2BCE37A300F04682 /* AppDelegate+Shared.swift */; }; + 84DC5FFF2BCE37A300F04682 /* AppDelegate+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DC5FFD2BCE37A300F04682 /* AppDelegate+Shared.swift */; }; + 84DC60002BCE37A300F04682 /* AppDelegate+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DC5FFD2BCE37A300F04682 /* AppDelegate+Shared.swift */; }; + 84DC60022BCE40B200F04682 /* Images in Frameworks */ = {isa = PBXBuildFile; productRef = 84DC60012BCE40B200F04682 /* Images */; }; + 84DC60042BCE40D000F04682 /* ParserObjC in Frameworks */ = {isa = PBXBuildFile; productRef = 84DC60032BCE40D000F04682 /* ParserObjC */; }; 84DCA5122BABB75600792720 /* FoundationExtras in Frameworks */ = {isa = PBXBuildFile; productRef = 84DCA5112BABB75600792720 /* FoundationExtras */; }; 84DCA5142BABB76100792720 /* AppKitExtras in Frameworks */ = {isa = PBXBuildFile; productRef = 84DCA5132BABB76100792720 /* AppKitExtras */; }; 84DCA5162BABB76B00792720 /* CloudKitExtras in Frameworks */ = {isa = PBXBuildFile; productRef = 84DCA5152BABB76B00792720 /* CloudKitExtras */; }; @@ -734,7 +710,6 @@ 84E185B3203B74E500F69BFA /* SingleLineTextFieldSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */; }; 84E185C3203BB12600F69BFA /* MultilineTextFieldSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */; }; 84E46C7D1F75EF7B005ECFB3 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */; }; - 84E850861FCB60CE0072EA88 /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; }; 84E8E0DB202EC49300562D8F /* TimelineViewController+ContextualMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */; }; 84E8E0EB202F693600562D8F /* DetailWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8E0EA202F693600562D8F /* DetailWebView.swift */; }; 84E95D241FB1087500552D99 /* ArticlePasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */; }; @@ -758,7 +733,6 @@ 84F9EAF3213660A100CF2DE4 /* testCurrentArticleIsNil.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE0213660A100CF2DE4 /* testCurrentArticleIsNil.applescript */; }; 84F9EAF4213660A100CF2DE4 /* testGenericScript.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE1213660A100CF2DE4 /* testGenericScript.applescript */; }; 84F9EAF5213660A100CF2DE4 /* establishMainWindowStartingState.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */; }; - 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; B24E9ADC245AB88400DA5718 /* NSAttributedString+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */; }; B24E9ADD245AB88400DA5718 /* NSAttributedString+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */; }; B24E9ADE245AB88400DA5718 /* NSAttributedString+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */; }; @@ -1093,7 +1067,6 @@ 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = ""; }; 5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorIconHeaderView.swift; sourceTree = ""; }; 51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSapp_target.xcconfig; sourceTree = ""; }; - 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSImage-Extensions.swift"; sourceTree = ""; }; 5117715424E1EA0F00A2A836 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = ""; }; 511B9805237DCAC90028BCAA /* UserInfoKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoKey.swift; sourceTree = ""; }; 511D43EE231FBDE800FB1562 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreenPad.storyboard; sourceTree = ""; }; @@ -1152,7 +1125,6 @@ 516A093F2361240900EAE89B /* Account.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Account.storyboard; sourceTree = ""; }; 516A09412361248000EAE89B /* Inspector.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Inspector.storyboard; sourceTree = ""; }; 516AE9B22371C372007DEEAA /* FeedTableViewSectionHeaderLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedTableViewSectionHeaderLayout.swift; sourceTree = ""; }; - 516AE9DE2372269A007DEEAA /* IconImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconImage.swift; sourceTree = ""; }; 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareFolderPickerController.swift; sourceTree = ""; }; 517630032336215100E15FFF /* main.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = main.js; sourceTree = ""; }; 517A745A2443665000B553B9 /* UIPageViewController-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIPageViewController-Extensions.swift"; sourceTree = ""; }; @@ -1243,7 +1215,6 @@ 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleStatusSyncTimer.swift; sourceTree = ""; }; 51EAED95231363EF00A9EEE3 /* NonIntrinsicButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicButton.swift; sourceTree = ""; }; 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FolderTreeMenu.swift; path = AddFeed/FolderTreeMenu.swift; sourceTree = ""; }; - 51EF0F76227716200050506E /* FaviconGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconGenerator.swift; sourceTree = ""; }; 51EF0F7D2277A57D0050506E /* TimelineAccessibilityCellLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineAccessibilityCellLayout.swift; sourceTree = ""; }; 51EF0F7F2277A8330050506E /* TimelineCellLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineCellLayout.swift; sourceTree = ""; }; 51F805D32428499E0022C792 /* NetNewsWire-dev.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "NetNewsWire-dev.entitlements"; sourceTree = ""; }; @@ -1304,10 +1275,7 @@ 841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuiltinSmartFeedInspectorViewController.swift; sourceTree = ""; }; 841CECD62BAD03C60001EE72 /* Tree */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Tree; sourceTree = ""; }; 84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWebViewController.swift; sourceTree = ""; }; - 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedIconDownloader.swift; sourceTree = ""; }; 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadataDownloader.swift; sourceTree = ""; }; - 8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedImageDownloader.swift; sourceTree = ""; }; - 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSHTMLMetadata+Extension.swift"; sourceTree = ""; }; 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppNotifications.swift; sourceTree = ""; }; 842E45DC1ED8C54B000A8B52 /* Browser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Browser.swift; sourceTree = ""; }; 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmallIconProvider.swift; sourceTree = ""; }; @@ -1320,10 +1288,8 @@ 844B5B681FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = SidebarKeyboardShortcuts.plist; sourceTree = ""; }; 8451226D2B8CEA9100480DB0 /* SidebarItemIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SidebarItemIdentifier.swift; path = Shared/SidebarItem/SidebarItemIdentifier.swift; sourceTree = SOURCE_ROOT; }; 8451226E2B8CEA9100480DB0 /* SidebarItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SidebarItem.swift; path = Shared/SidebarItem/SidebarItem.swift; sourceTree = SOURCE_ROOT; }; - 845213221FCA5B10003B6E93 /* ImageDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageDownloader.swift; sourceTree = ""; }; 845479871FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = TimelineKeyboardShortcuts.plist; sourceTree = ""; }; 8454C3F2263F2D8700E3F9C7 /* IconImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconImageCache.swift; sourceTree = ""; }; - 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleFaviconDownloader.swift; sourceTree = ""; }; 845A29211FC9251E007B49E3 /* SidebarCellLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarCellLayout.swift; sourceTree = ""; }; 845A29231FC9255E007B49E3 /* SidebarCellAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarCellAppearance.swift; sourceTree = ""; }; 845B14A51FC2299E0013CF92 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; @@ -1345,7 +1311,6 @@ 8483630A2262A3F000DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/RenameSheet.xib; sourceTree = SOURCE_ROOT; }; 848B937121C8C5540038DC0D /* CrashReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrashReporter.swift; sourceTree = ""; }; 848D578D21543519005FFAD5 /* PasteboardFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteboardFeed.swift; sourceTree = ""; }; - 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FaviconDownloader.swift; sourceTree = ""; }; 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFolderWindowController.swift; sourceTree = ""; }; 849A97511ED9EAC0007D329B /* AddFeedController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedController.swift; path = AddFeed/AddFeedController.swift; sourceTree = ""; }; 849A97521ED9EAC0007D329B /* AddFeedWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedWindowController.swift; path = AddFeed/AddFeedWindowController.swift; sourceTree = ""; }; @@ -1426,6 +1391,7 @@ 84D2200922B0BC4B0019E085 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; }; 84D52E941FE588BB00D14F5B /* DetailStatusBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailStatusBarView.swift; sourceTree = ""; }; 84DC5FF82BCE308500F04682 /* Images */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Images; sourceTree = ""; }; + 84DC5FFD2BCE37A300F04682 /* AppDelegate+Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Shared.swift"; sourceTree = ""; }; 84DCA50D2BAB643700792720 /* FoundationExtras */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = FoundationExtras; sourceTree = ""; }; 84DCA50E2BABB5D800792720 /* AppKitExtras */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = AppKitExtras; sourceTree = ""; }; 84DCA50F2BABB65600792720 /* CloudKitExtras */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = CloudKitExtras; sourceTree = ""; }; @@ -1435,7 +1401,6 @@ 84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleLineTextFieldSizer.swift; sourceTree = ""; }; 84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineTextFieldSizer.swift; sourceTree = ""; }; 84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDefaults.swift; sourceTree = ""; }; - 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorAvatarDownloader.swift; sourceTree = ""; }; 84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimelineViewController+ContextualMenus.swift"; sourceTree = ""; }; 84E8E0EA202F693600562D8F /* DetailWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWebView.swift; sourceTree = ""; }; 84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticlePasteboardWriter.swift; sourceTree = ""; }; @@ -1464,7 +1429,6 @@ 84FB9FAC2BC33AFE00B7AFC3 /* NewsBlur */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = NewsBlur; sourceTree = ""; }; 84FB9FAD2BC344F800B7AFC3 /* Feedbin */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Feedbin; sourceTree = ""; }; 84FB9FAE2BC3494B00B7AFC3 /* FeedFinder */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = FeedFinder; sourceTree = ""; }; - 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconURLFinder.swift; sourceTree = ""; }; B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+NetNewsWire.swift"; sourceTree = ""; }; B24EFD482330FF99006C6242 /* NetNewsWire-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NetNewsWire-Bridging-Header.h"; sourceTree = ""; }; B24EFD5923310109006C6242 /* WKPreferencesPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKPreferencesPrivate.h; sourceTree = ""; }; @@ -1531,8 +1495,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 84DC60022BCE40B200F04682 /* Images in Frameworks */, 841CECDE2BAD06D10001EE72 /* Tree in Frameworks */, 51BC2F3824D3439A00E90810 /* Account in Frameworks */, + 84DC60042BCE40D000F04682 /* ParserObjC in Frameworks */, 84D9582C2BABE53B0053E7B2 /* FoundationExtras in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2161,18 +2127,6 @@ path = SidebarItem; sourceTree = ""; }; - 845213211FCA5B10003B6E93 /* Images */ = { - isa = PBXGroup; - children = ( - 845213221FCA5B10003B6E93 /* ImageDownloader.swift */, - 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */, - 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */, - 8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */, - 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */, - ); - path = Images; - sourceTree = ""; - }; 845A29251FC928C7007B49E3 /* Cell */ = { isa = PBXGroup; children = ( @@ -2203,17 +2157,6 @@ path = CrashReporter; sourceTree = ""; }; - 848F6AE31FC29CFA002D422E /* Favicons */ = { - isa = PBXGroup; - children = ( - 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */, - 51EF0F76227716200050506E /* FaviconGenerator.swift */, - 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */, - 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */, - ); - path = Favicons; - sourceTree = ""; - }; 849A97411ED9EAA9007D329B /* Add Folder */ = { isa = PBXGroup; children = ( @@ -2242,12 +2185,10 @@ 849A97731ED9EC04007D329B /* ArticleStringFormatter.swift */, 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */, 5108F6B52375E612001ABC45 /* CacheCleaner.swift */, - 516AE9DE2372269A007DEEAA /* IconImage.swift */, 849A97971ED9EFAA007D329B /* Node-Extensions.swift */, B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */, 8405DD9B22153BD7008CE1BF /* NSView-Extensions.swift */, B2B8075D239C49D300F191E0 /* RSImage-AppIcons.swift */, - 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */, 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */, 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */, 847120D62B8AE6AF00BBFC34 /* UTType+Extensions.swift */, @@ -2451,6 +2392,7 @@ 511B9805237DCAC90028BCAA /* UserInfoKey.swift */, 844933D12BA953590068AC51 /* ArticlePathInfo.swift */, 8454C3F2263F2D8700E3F9C7 /* IconImageCache.swift */, + 84DC5FFD2BCE37A300F04682 /* AppDelegate+Shared.swift */, 845122752B8CEA9B00480DB0 /* SidebarItem */, 51C452AD2265102800C03939 /* Timeline */, 84702AB31FA27AE8006B8943 /* Commands */, @@ -2462,8 +2404,6 @@ 51FE0FF9234552490056195D /* UserNotifications */, 84F2D5341FC22FCB00998D64 /* SmartFeeds */, 51B5C85A23F22A7A00032075 /* ShareExtension */, - 848F6AE31FC29CFA002D422E /* Favicons */, - 845213211FCA5B10003B6E93 /* Images */, 8426119C1FCB6ED40086A189 /* HTMLMetadata */, 5183CCEA226F70350010922C /* Timer */, 512E08DD22687FA000BDCFDD /* Tree */, @@ -2826,6 +2766,8 @@ 51BC2F3724D3439A00E90810 /* Account */, 84D9582B2BABE53B0053E7B2 /* FoundationExtras */, 841CECDD2BAD06D10001EE72 /* Tree */, + 84DC60012BCE40B200F04682 /* Images */, + 84DC60032BCE40D000F04682 /* ParserObjC */, ); productName = "NetNewsWire iOS Share Extension"; productReference = 513C5CE6232571C2003D4054 /* NetNewsWire iOS Share Extension.appex */; @@ -3689,7 +3631,6 @@ 51B5C8BA23F368D000032075 /* ExtensionContainersFile.swift in Sources */, 51B5C8BB23F368D000032075 /* ExtensionFeedAddRequest.swift in Sources */, 51A9A5E82380CA130033AADF /* ShareFolderPickerCell.swift in Sources */, - 51A9A5EF2380D63B0033AADF /* IconImage.swift in Sources */, 51A9A5ED2380D6000033AADF /* AppAssets.swift in Sources */, 51B5C8C123F3A0DB00032075 /* ExtensionFeedAddRequestFile.swift in Sources */, 51A9A5E12380C4FE0033AADF /* AppDefaults.swift in Sources */, @@ -3766,13 +3707,13 @@ 65ED3FD7235DEF6C0081F399 /* NSApplication+Scriptability.swift in Sources */, 65ED3FD8235DEF6C0081F399 /* NSView-Extensions.swift in Sources */, 5103A9F824225E4C00410853 /* AccountsAddCloudKitWindowController.swift in Sources */, + 84DC5FFF2BCE37A300F04682 /* AppDelegate+Shared.swift in Sources */, 65ED3FD9235DEF6C0081F399 /* SidebarCell.swift in Sources */, 65ED3FDA235DEF6C0081F399 /* ArticleStatusSyncTimer.swift in Sources */, 65ED3FDB235DEF6C0081F399 /* FeedTreeControllerDelegate.swift in Sources */, 65ED3FDC235DEF6C0081F399 /* UnreadCountView.swift in Sources */, 65ED3FDD235DEF6C0081F399 /* ActivityType.swift in Sources */, 65ED3FDE235DEF6C0081F399 /* CrashReportWindowController.swift in Sources */, - 65ED3FDF235DEF6C0081F399 /* FeedIconDownloader.swift in Sources */, B24E9ADD245AB88400DA5718 /* NSAttributedString+NetNewsWire.swift in Sources */, 510C417C24E5D1AE008226FD /* ExtensionFeedAddRequestFile.swift in Sources */, 510C417D24E5D1AE008226FD /* ExtensionContainers.swift in Sources */, @@ -3785,10 +3726,8 @@ 65ED3FE6235DEF6C0081F399 /* RenameWindowController.swift in Sources */, 65ED3FE7235DEF6C0081F399 /* SendToMicroBlogCommand.swift in Sources */, 65ED3FE8235DEF6C0081F399 /* ArticleTheme.swift in Sources */, - 65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */, 6538131C2680E17F007A082C /* UserInfoKey.swift in Sources */, 65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */, - 65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */, 518C3194237B00DA004D740F /* DetailIconSchemeHandler.swift in Sources */, 65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */, 65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */, @@ -3815,7 +3754,6 @@ 65ED3FFF235DEF6C0081F399 /* SidebarOutlineDataSource.swift in Sources */, 65ED4000235DEF6C0081F399 /* SidebarCellAppearance.swift in Sources */, 65ED4001235DEF6C0081F399 /* StarredFeedDelegate.swift in Sources */, - 65ED4002235DEF6C0081F399 /* FaviconDownloader.swift in Sources */, 65ED4003235DEF6C0081F399 /* AdvancedPreferencesViewController.swift in Sources */, 65ED4004235DEF6C0081F399 /* SharingServicePickerDelegate.swift in Sources */, 65ED4005235DEF6C0081F399 /* Node-Extensions.swift in Sources */, @@ -3828,7 +3766,6 @@ 65ED400B235DEF6C0081F399 /* TodayFeedDelegate.swift in Sources */, 65ED400C235DEF6C0081F399 /* FolderInspectorViewController.swift in Sources */, 65ED400D235DEF6C0081F399 /* SmartFeedDelegate.swift in Sources */, - 65ED400E235DEF6C0081F399 /* ImageDownloader.swift in Sources */, 65ED400F235DEF6C0081F399 /* LegacyArticleExtractorButton.swift in Sources */, 65ED4011235DEF6C0081F399 /* AddFolderWindowController.swift in Sources */, 65ED4012235DEF6C0081F399 /* TimelineContainerViewController.swift in Sources */, @@ -3843,7 +3780,6 @@ 65ED4019235DEF6C0081F399 /* FetchRequestOperation.swift in Sources */, 65ED401A235DEF6C0081F399 /* HTMLMetadataDownloader.swift in Sources */, 65ED401B235DEF6C0081F399 /* TimelineViewController.swift in Sources */, - 65ED401C235DEF6C0081F399 /* FaviconGenerator.swift in Sources */, 65ED401D235DEF6C0081F399 /* RefreshInterval.swift in Sources */, 65ED401E235DEF6C0081F399 /* TimelineCellData.swift in Sources */, 65ED401F235DEF6C0081F399 /* BuiltinSmartFeedInspectorViewController.swift in Sources */, @@ -3871,7 +3807,6 @@ 847120D82B8AE6AF00BBFC34 /* UTType+Extensions.swift in Sources */, 510C417E24E5D1AE008226FD /* ExtensionFeedAddRequest.swift in Sources */, 51868BF2254386630011A17B /* SidebarDeleteItemsAlert.swift in Sources */, - 6538131B2680E176007A082C /* IconImage.swift in Sources */, 65ED4032235DEF6C0081F399 /* FetchRequestQueue.swift in Sources */, 65ED4033235DEF6C0081F399 /* SidebarKeyboardDelegate.swift in Sources */, 65ED4034235DEF6C0081F399 /* AccountsPreferencesViewController.swift in Sources */, @@ -3879,10 +3814,7 @@ 65ED4036235DEF6C0081F399 /* NNW3ImportController.swift in Sources */, 65ED4037235DEF6C0081F399 /* FolderTreeControllerDelegate.swift in Sources */, 845122732B8CEA9100480DB0 /* SidebarItem.swift in Sources */, - 65ED4038235DEF6C0081F399 /* RSImage-Extensions.swift in Sources */, - 65ED4039235DEF6C0081F399 /* SingleFaviconDownloader.swift in Sources */, 65ED403A235DEF6C0081F399 /* Feed+Scriptability.swift in Sources */, - 65ED403B235DEF6C0081F399 /* AuthorAvatarDownloader.swift in Sources */, 65ED403C235DEF6C0081F399 /* SingleLineTextFieldSizer.swift in Sources */, 65ED403D235DEF6C0081F399 /* TimelineTableCellView.swift in Sources */, 65ED403E235DEF6C0081F399 /* TimelineCellAppearance.swift in Sources */, @@ -3927,7 +3859,6 @@ 179D280D26F73D83003B2E0A /* ArticleThemePlist.swift in Sources */, 51C45296226509D300C03939 /* OPMLExporter.swift in Sources */, 51C45291226509C800C03939 /* SmartFeed.swift in Sources */, - 51C452A722650A3D00C03939 /* RSImage-Extensions.swift in Sources */, 511B9807237DCAC90028BCAA /* UserInfoKey.swift in Sources */, 51C45269226508F600C03939 /* FeedTableViewCell.swift in Sources */, 51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */, @@ -3956,7 +3887,6 @@ 51C4526B226508F600C03939 /* SidebarViewController.swift in Sources */, 5126EE97226CB48A00C22AFC /* SceneCoordinator.swift in Sources */, 84CAFCB022BC8C35007694F0 /* FetchRequestOperation.swift in Sources */, - 51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */, 51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */, 51C4525A226508D600C03939 /* UIStoryboard-Extensions.swift in Sources */, 517A745B2443665000B553B9 /* UIPageViewController-Extensions.swift in Sources */, @@ -3968,20 +3898,16 @@ 5F323809231DF9F000706F6B /* VibrantTableViewCell.swift in Sources */, 51FE10042345529D0056195D /* UserNotificationManager.swift in Sources */, 51C4CFF224D37D1F00AF9874 /* Secrets.swift in Sources */, - 51C452A022650A1900C03939 /* FeedIconDownloader.swift in Sources */, 845122712B8CEA9100480DB0 /* SidebarItemIdentifier.swift in Sources */, - 51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */, 51A66685238075AE00CB272D /* AddFeedDefaultContainer.swift in Sources */, 176813E92564BAE200D98635 /* WidgetDeepLinks.swift in Sources */, 51B5C87723F22B8200032075 /* ExtensionContainers.swift in Sources */, 51C45292226509C800C03939 /* TodayFeedDelegate.swift in Sources */, - 51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */, 51B5C87B23F2317700032075 /* ExtensionFeedAddRequest.swift in Sources */, 51627A93238A3836007B3B4B /* CroppingPreviewParameters.swift in Sources */, 512AF9DD236F05230066F8BE /* InteractiveLabel.swift in Sources */, 51E3EB3D229AB08300645299 /* ErrorHandler.swift in Sources */, 5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */, - 51C4529D22650A1000C03939 /* FaviconURLFinder.swift in Sources */, 5142192A23522B5500E07E2C /* ImageViewController.swift in Sources */, 516244E3241E19F000B61C47 /* ColorPaletteTableViewController.swift in Sources */, 51C45258226508CF00C03939 /* AppAssets.swift in Sources */, @@ -4006,7 +3932,6 @@ C5A6ED6D23C9B0C800AB6BE2 /* UIActivityViewController-Extensions.swift in Sources */, 5108F6D42375EEEF001ABC45 /* TimelinePreviewTableViewController.swift in Sources */, 84CAFCA522BC8C08007694F0 /* FetchRequestQueue.swift in Sources */, - 51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */, 17D643B226F8A436008D4C05 /* ArticleThemeDownloader.swift in Sources */, 51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */, 51F9F3F723DF6DB200A314FD /* ArticleIconSchemeHandler.swift in Sources */, @@ -4022,6 +3947,7 @@ 51C452AC22650FD200C03939 /* AppNotifications.swift in Sources */, 51EF0F7E2277A57D0050506E /* TimelineAccessibilityCellLayout.swift in Sources */, 51A1699B235E10D700EB091F /* AccountInspectorViewController.swift in Sources */, + 84DC60002BCE37A300F04682 /* AppDelegate+Shared.swift in Sources */, 512D554423C804DE0023FFFA /* OpenInSafariActivity.swift in Sources */, 51C452762265091600C03939 /* TimelineViewController.swift in Sources */, 5195C1DC2720BD3000888867 /* FeedRowIdentifier.swift in Sources */, @@ -4044,15 +3970,12 @@ 17D7586F2679C21800B17787 /* OnePasswordExtension.m in Sources */, 844933D42BA953590068AC51 /* ArticlePathInfo.swift in Sources */, 17071EF126F8137400F5E71D /* ArticleTheme+Notifications.swift in Sources */, - 51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */, 84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */, 512E09012268907400BDCFDD /* FeedTableViewSectionHeader.swift in Sources */, - 516AE9E02372269A007DEEAA /* IconImage.swift in Sources */, 51C45268226508F600C03939 /* FeedUnreadCountView.swift in Sources */, D3A39865246505DF00F9A366 /* FindInArticleActivity.swift in Sources */, 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */, 5137C2EA26F63AE6009EFEDB /* ArticleThemeImporter.swift in Sources */, - 51C4529F22650A1900C03939 /* AuthorAvatarDownloader.swift in Sources */, 5108F6D22375EED2001ABC45 /* TimelineCustomizerViewController.swift in Sources */, 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */, FFD43E412340F488009E5CA3 /* MarkAsReadAlertController.swift in Sources */, @@ -4097,7 +4020,6 @@ 84F2D53A1FC2308B00998D64 /* UnreadFeed.swift in Sources */, 845A29221FC9251E007B49E3 /* SidebarCellLayout.swift in Sources */, 510C418224E5D1AE008226FD /* ExtensionFeedAddRequest.swift in Sources */, - 516AE9DF2372269A007DEEAA /* IconImage.swift in Sources */, 84AD1EBA2031649C00BC20B7 /* SmartFeedPasteboardWriter.swift in Sources */, 849C78922362AB04009A71E4 /* ExportOPMLWindowController.swift in Sources */, 84CC88181FE59CBF00644329 /* SmartFeedsController.swift in Sources */, @@ -4121,7 +4043,6 @@ 510C418024E5D1AE008226FD /* ExtensionFeedAddRequestFile.swift in Sources */, 51FE10092346739D0056195D /* ActivityType.swift in Sources */, 840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */, - 8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */, 84C9FC7B22629E1200D921D6 /* PreferencesControlsBackgroundView.swift in Sources */, 84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */, 17D643B126F8A436008D4C05 /* ArticleThemeDownloader.swift in Sources */, @@ -4133,9 +4054,7 @@ 84A14FF320048CA70046AD9A /* SendToMicroBlogCommand.swift in Sources */, B2B8075E239C49D300F191E0 /* RSImage-AppIcons.swift in Sources */, 849A97891ED9ECEF007D329B /* ArticleTheme.swift in Sources */, - 84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */, 84B7178C201E66580091657D /* SidebarViewController+ContextualMenus.swift in Sources */, - 842611A21FCB769D0086A189 /* RSHTMLMetadata+Extension.swift in Sources */, 84A1500520048DDF0046AD9A /* SendToMarsEditCommand.swift in Sources */, 51FE10032345529D0056195D /* UserNotificationManager.swift in Sources */, D5907DB22004BB37005947E5 /* ScriptingObjectContainer.swift in Sources */, @@ -4163,7 +4082,6 @@ 84AD1EBC2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift in Sources */, 845A29241FC9255E007B49E3 /* SidebarCellAppearance.swift in Sources */, 845EE7B11FC2366500854A1F /* StarredFeedDelegate.swift in Sources */, - 848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */, 511B9806237DCAC90028BCAA /* UserInfoKey.swift in Sources */, 84C9FC7722629E1200D921D6 /* AdvancedPreferencesViewController.swift in Sources */, 849EE72120391F560082A1EA /* SharingServicePickerDelegate.swift in Sources */, @@ -4179,11 +4097,11 @@ 841ABA5E20145E9200980E11 /* FolderInspectorViewController.swift in Sources */, 84DEE56522C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */, 845122722B8CEA9100480DB0 /* SidebarItem.swift in Sources */, - 845213231FCA5B11003B6E93 /* ImageDownloader.swift in Sources */, 51FA73B72332D5F70090D516 /* LegacyArticleExtractorButton.swift in Sources */, 849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */, 8405DDA522168C62008CE1BF /* TimelineContainerViewController.swift in Sources */, 844B5B671FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift in Sources */, + 84DC5FFE2BCE37A300F04682 /* AppDelegate+Shared.swift in Sources */, 848D578E21543519005FFAD5 /* PasteboardFeed.swift in Sources */, 5144EA2F2279FAB600D19003 /* AccountsDetailViewController.swift in Sources */, 849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */, @@ -4195,7 +4113,6 @@ 84CAFCAF22BC8C35007694F0 /* FetchRequestOperation.swift in Sources */, 8426119E1FCB6ED40086A189 /* HTMLMetadataDownloader.swift in Sources */, 849A976E1ED9EBC8007D329B /* TimelineViewController.swift in Sources */, - 5154368B229404D1005E1CDF /* FaviconGenerator.swift in Sources */, 5183CCE6226F4E110010922C /* RefreshInterval.swift in Sources */, 849A97771ED9EC04007D329B /* TimelineCellData.swift in Sources */, 841ABA6020145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift in Sources */, @@ -4229,10 +4146,7 @@ 849ADEE42359817E000E1B81 /* NNW3ImportController.swift in Sources */, 179C39EB26F76B3800D4E741 /* ArticleThemePlist.swift in Sources */, 849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */, - 51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */, - 845A29091FC74B8E007B49E3 /* SingleFaviconDownloader.swift in Sources */, D5F4EDB720074D6500B9E363 /* Feed+Scriptability.swift in Sources */, - 84E850861FCB60CE0072EA88 /* AuthorAvatarDownloader.swift in Sources */, 84E185B3203B74E500F69BFA /* SingleLineTextFieldSizer.swift in Sources */, 849A977A1ED9EC04007D329B /* TimelineTableCellView.swift in Sources */, 849A97761ED9EC04007D329B /* TimelineCellAppearance.swift in Sources */, @@ -4957,6 +4871,14 @@ isa = XCSwiftPackageProductDependency; productName = Images; }; + 84DC60012BCE40B200F04682 /* Images */ = { + isa = XCSwiftPackageProductDependency; + productName = Images; + }; + 84DC60032BCE40D000F04682 /* ParserObjC */ = { + isa = XCSwiftPackageProductDependency; + productName = ParserObjC; + }; 84DCA5112BABB75600792720 /* FoundationExtras */ = { isa = XCSwiftPackageProductDependency; productName = FoundationExtras; diff --git a/Shared/Activity/ActivityManager.swift b/Shared/Activity/ActivityManager.swift index 09304ace8..029a41ed9 100644 --- a/Shared/Activity/ActivityManager.swift +++ b/Shared/Activity/ActivityManager.swift @@ -12,6 +12,7 @@ import Account import Articles import Intents import UniformTypeIdentifiers +import Images #if os(iOS) @preconcurrency import CoreSpotlight @@ -159,7 +160,7 @@ import CoreSpotlight #endif @objc func feedIconDidBecomeAvailable(_ note: Notification) { - guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed, let activityFeedID = selectingActivity?.userInfo?[ArticlePathKey.feedID] as? String else { + guard let feed = note.userInfo?[FeedIconDownloader.feedKey] as? Feed, let activityFeedID = selectingActivity?.userInfo?[ArticlePathKey.feedID] as? String else { return } diff --git a/Shared/AppDelegate+Shared.swift b/Shared/AppDelegate+Shared.swift new file mode 100644 index 000000000..c4cfd4fd1 --- /dev/null +++ b/Shared/AppDelegate+Shared.swift @@ -0,0 +1,23 @@ +// +// AppDelegate+Shared.swift +// NetNewsWire +// +// Created by Brent Simmons on 4/15/24. +// Copyright © 2024 Ranchero Software. All rights reserved. +// + +import Foundation +import Images +import ParserObjC + +extension AppDelegate: FaviconDownloaderDelegate, FeedIconDownloaderDelegate { + + var appIconImage: IconImage? { + IconImage.appIcon + } + + func downloadMetadata(_ url: String) async throws -> RSHTMLMetadata? { + + try await HTMLMetadataDownloader.downloadMetadata(for: url) + } +} diff --git a/Shared/Extensions/ArticleUtilities.swift b/Shared/Extensions/ArticleUtilities.swift index b2cbec712..500efff6e 100644 --- a/Shared/Extensions/ArticleUtilities.swift +++ b/Shared/Extensions/ArticleUtilities.swift @@ -9,6 +9,7 @@ import Foundation import Articles import Account +import Images // These handle multiple accounts. diff --git a/Shared/Extensions/RSImage-AppIcons.swift b/Shared/Extensions/RSImage-AppIcons.swift index 49caf3b65..3c312c97d 100644 --- a/Shared/Extensions/RSImage-AppIcons.swift +++ b/Shared/Extensions/RSImage-AppIcons.swift @@ -8,6 +8,7 @@ import Foundation import Core +import Images extension RSImage { static let appIconImage: RSImage? = { diff --git a/Shared/Extensions/SmallIconProvider.swift b/Shared/Extensions/SmallIconProvider.swift index 81a7ce7a7..755f9f44f 100644 --- a/Shared/Extensions/SmallIconProvider.swift +++ b/Shared/Extensions/SmallIconProvider.swift @@ -9,6 +9,7 @@ import Foundation import Articles import Account +import Images protocol SmallIconProvider { diff --git a/Shared/IconImageCache.swift b/Shared/IconImageCache.swift index 9fa180af7..61786267e 100644 --- a/Shared/IconImageCache.swift +++ b/Shared/IconImageCache.swift @@ -9,6 +9,7 @@ import Foundation import Account import Articles +import Images @MainActor final class IconImageCache { diff --git a/Shared/Images/FeaturedImageDownloader.swift b/Shared/Images/FeaturedImageDownloader.swift deleted file mode 100644 index c9177ea0c..000000000 --- a/Shared/Images/FeaturedImageDownloader.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// FeaturedImageDownloader.swift -// NetNewsWire -// -// Created by Brent Simmons on 11/26/17. -// Copyright © 2017 Ranchero Software. All rights reserved. -// - -import Foundation -import Articles -import Parser - -final class FeaturedImageDownloader { - - private let imageDownloader: ImageDownloader - private var articleURLToFeaturedImageURLCache = [String: String]() - private var articleURLsWithNoFeaturedImage = Set() - private var urlsInProgress = Set() - - init(imageDownloader: ImageDownloader) { - - self.imageDownloader = imageDownloader - } - - func image(for article: Article) -> RSImage? { - - if let imageLink = article.imageLink { - return image(forFeaturedImageURL: imageLink) - } - if let link = article.link { - return image(forArticleURL: link) - } - return nil - } - - func image(forArticleURL articleURL: String) -> RSImage? { - - if articleURLsWithNoFeaturedImage.contains(articleURL) { - return nil - } - - if let featuredImageURL = cachedURL(for: articleURL) { - return image(forFeaturedImageURL: featuredImageURL) - } - findFeaturedImageURL(for: articleURL) - return nil - } - - func image(forFeaturedImageURL featuredImageURL: String) -> RSImage? { - if let data = imageDownloader.image(for: featuredImageURL) { - return RSImage(data: data) - } - return nil - } - -} - -private extension FeaturedImageDownloader { - - func cachedURL(for articleURL: String) -> String? { - - return articleURLToFeaturedImageURLCache[articleURL] - } - - func cacheURL(for articleURL: String, _ featuredImageURL: String) { - - articleURLsWithNoFeaturedImage.remove(articleURL) - articleURLToFeaturedImageURLCache[articleURL] = featuredImageURL - } - - func findFeaturedImageURL(for articleURL: String) { - - guard !urlsInProgress.contains(articleURL) else { - return - } - urlsInProgress.insert(articleURL) - - HTMLMetadataDownloader.downloadMetadata(for: articleURL) { (metadata) in - - self.urlsInProgress.remove(articleURL) - - guard let metadata = metadata else { - return - } - self.pullFeaturedImageURL(from: metadata, articleURL: articleURL) - } - } - - func pullFeaturedImageURL(from metadata: RSHTMLMetadata, articleURL: String) { - - if let url = metadata.bestFeaturedImageURL() { - cacheURL(for: articleURL, url) - let _ = image(forFeaturedImageURL: url) - return - } - - articleURLsWithNoFeaturedImage.insert(articleURL) - } -} diff --git a/Shared/SmartFeeds/SearchFeedDelegate.swift b/Shared/SmartFeeds/SearchFeedDelegate.swift index 5c0406722..b6b14f939 100644 --- a/Shared/SmartFeeds/SearchFeedDelegate.swift +++ b/Shared/SmartFeeds/SearchFeedDelegate.swift @@ -10,6 +10,7 @@ import Foundation import Account import Articles import ArticlesDatabase +import Images @MainActor struct SearchFeedDelegate: SmartFeedDelegate { diff --git a/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift b/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift index a82cb3383..589448355 100644 --- a/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift +++ b/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift @@ -10,6 +10,7 @@ import Foundation import Account import Articles import ArticlesDatabase +import Images @MainActor struct SearchTimelineFeedDelegate: SmartFeedDelegate { diff --git a/Shared/SmartFeeds/SmartFeed.swift b/Shared/SmartFeeds/SmartFeed.swift index 472a0a089..72a98c519 100644 --- a/Shared/SmartFeeds/SmartFeed.swift +++ b/Shared/SmartFeeds/SmartFeed.swift @@ -12,6 +12,7 @@ import ArticlesDatabase import Account import Database import Core +import Images final class SmartFeed: PseudoFeed { diff --git a/Shared/SmartFeeds/StarredFeedDelegate.swift b/Shared/SmartFeeds/StarredFeedDelegate.swift index 82dd42a3d..5b081a2ed 100644 --- a/Shared/SmartFeeds/StarredFeedDelegate.swift +++ b/Shared/SmartFeeds/StarredFeedDelegate.swift @@ -10,8 +10,7 @@ import Foundation import Articles import ArticlesDatabase import Account - -// Main thread only. +import Images @MainActor struct StarredFeedDelegate: SmartFeedDelegate { diff --git a/Shared/SmartFeeds/TodayFeedDelegate.swift b/Shared/SmartFeeds/TodayFeedDelegate.swift index ca2190aa6..203ff40dc 100644 --- a/Shared/SmartFeeds/TodayFeedDelegate.swift +++ b/Shared/SmartFeeds/TodayFeedDelegate.swift @@ -10,6 +10,7 @@ import Foundation import Articles import ArticlesDatabase import Account +import Images @MainActor struct TodayFeedDelegate: SmartFeedDelegate { diff --git a/Shared/SmartFeeds/UnreadFeed.swift b/Shared/SmartFeeds/UnreadFeed.swift index eaecec21b..2d690c1e0 100644 --- a/Shared/SmartFeeds/UnreadFeed.swift +++ b/Shared/SmartFeeds/UnreadFeed.swift @@ -14,6 +14,7 @@ import Foundation import Account import Articles import ArticlesDatabase +import Images // This just shows the global unread count, which appDelegate already has. Easy. diff --git a/iOS/AppAssets.swift b/iOS/AppAssets.swift index 5a1a9971e..d12c6776c 100644 --- a/iOS/AppAssets.swift +++ b/iOS/AppAssets.swift @@ -9,6 +9,7 @@ import UIKit import Account import Core +import Images struct AppAssets { diff --git a/iOS/AppDefaults.swift b/iOS/AppDefaults.swift index 3f065810a..421314745 100644 --- a/iOS/AppDefaults.swift +++ b/iOS/AppDefaults.swift @@ -7,6 +7,7 @@ // import UIKit +import Images enum UserInterfaceColorPalette: Int, CustomStringConvertible, CaseIterable { case automatic = 0 diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift index 805c30964..ca28d7146 100644 --- a/iOS/AppDelegate.swift +++ b/iOS/AppDelegate.swift @@ -14,6 +14,7 @@ import os.log import Secrets import WidgetKit import Core +import Images @MainActor var appDelegate: AppDelegate! @@ -95,6 +96,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD DefaultFeedsImporter.importDefaultFeeds(account: localAccount) } + FaviconGenerator.faviconTemplateImage = AppAssets.faviconTemplateImage + registerBackgroundTasks() CacheCleaner.purgeIfNecessary() initializeDownloaders() @@ -241,7 +244,8 @@ private extension AppDelegate { let faviconsFolder = faviconsFolderURL.absoluteString let faviconsFolderPath = faviconsFolder.suffix(from: faviconsFolder.index(faviconsFolder.startIndex, offsetBy: 7)) faviconDownloader = FaviconDownloader(folder: String(faviconsFolderPath)) - + faviconDownloader.delegate = self + let imagesFolder = imagesFolderURL.absoluteString let imagesFolderPath = imagesFolder.suffix(from: imagesFolder.index(imagesFolder.startIndex, offsetBy: 7)) try! FileManager.default.createDirectory(at: imagesFolderURL, withIntermediateDirectories: true, attributes: nil) @@ -252,6 +256,7 @@ private extension AppDelegate { let tempFolder = tempDir.absoluteString let tempFolderPath = tempFolder.suffix(from: tempFolder.index(tempFolder.startIndex, offsetBy: 7)) feedIconDownloader = FeedIconDownloader(imageDownloader: imageDownloader, folder: String(tempFolderPath)) + feedIconDownloader.delegate = self } private func initializeHomeScreenQuickActions() { diff --git a/iOS/Article/ArticleIconSchemeHandler.swift b/iOS/Article/ArticleIconSchemeHandler.swift index 64b81b300..1fc2677fb 100644 --- a/iOS/Article/ArticleIconSchemeHandler.swift +++ b/iOS/Article/ArticleIconSchemeHandler.swift @@ -8,6 +8,7 @@ import Foundation import WebKit +import Images protocol ArticleIconSchemeHandlerDelegate: AnyObject { diff --git a/iOS/Article/WebViewController.swift b/iOS/Article/WebViewController.swift index 1c467fed4..53e3a2c33 100644 --- a/iOS/Article/WebViewController.swift +++ b/iOS/Article/WebViewController.swift @@ -14,6 +14,7 @@ import SafariServices import MessageUI import Core import ArticleExtractor +import Images protocol WebViewControllerDelegate: AnyObject { func webViewController(_: WebViewController, articleExtractorButtonStateDidUpdate: ArticleExtractorButtonState) diff --git a/iOS/Feeds/Cell/FeedTableViewCell.swift b/iOS/Feeds/Cell/FeedTableViewCell.swift index 960585381..946626684 100644 --- a/iOS/Feeds/Cell/FeedTableViewCell.swift +++ b/iOS/Feeds/Cell/FeedTableViewCell.swift @@ -9,6 +9,7 @@ import UIKit import Account import Tree +import Images protocol FeedTableViewCellDelegate: AnyObject { func feedTableViewCellDisclosureDidToggle(_ sender: FeedTableViewCell, expanding: Bool) diff --git a/iOS/Feeds/SidebarViewController.swift b/iOS/Feeds/SidebarViewController.swift index 8fdc1e128..ccafc8f69 100644 --- a/iOS/Feeds/SidebarViewController.swift +++ b/iOS/Feeds/SidebarViewController.swift @@ -12,6 +12,7 @@ import Articles import Tree import SafariServices import Core +import Images class SidebarViewController: UITableViewController, UndoableCommandRunner { @@ -126,7 +127,7 @@ class SidebarViewController: UITableViewController, UndoableCommandRunner { } @objc func feedIconDidBecomeAvailable(_ note: Notification) { - guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed else { + guard let feed = note.userInfo?[FeedIconDownloader.feedKey] as? Feed else { return } applyToCellsForRepresentedObject(feed, configureIcon(_:_:)) diff --git a/iOS/IconView.swift b/iOS/IconView.swift index db3f5e8c9..d26bb22c1 100644 --- a/iOS/IconView.swift +++ b/iOS/IconView.swift @@ -7,6 +7,7 @@ // import UIKit +import Images final class IconView: UIView { diff --git a/iOS/Inspector/FeedInspectorViewController.swift b/iOS/Inspector/FeedInspectorViewController.swift index ff8067c96..c1327eacd 100644 --- a/iOS/Inspector/FeedInspectorViewController.swift +++ b/iOS/Inspector/FeedInspectorViewController.swift @@ -10,6 +10,7 @@ import UIKit import Account import SafariServices import UserNotifications +import Images class FeedInspectorViewController: UITableViewController { diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 693b95eff..83feb79e2 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -14,6 +14,7 @@ import Tree import SafariServices import SwiftUI import Core +import Images protocol MainControllerIdentifiable { var mainControllerIdentifier: MainControllerIdentifier { get } diff --git a/iOS/Settings/TimelineCustomizerViewController.swift b/iOS/Settings/TimelineCustomizerViewController.swift index f9d15bb9a..6869be1aa 100644 --- a/iOS/Settings/TimelineCustomizerViewController.swift +++ b/iOS/Settings/TimelineCustomizerViewController.swift @@ -7,6 +7,7 @@ // import UIKit +import Images class TimelineCustomizerViewController: UIViewController { diff --git a/iOS/Settings/TimelinePreviewTableViewController.swift b/iOS/Settings/TimelinePreviewTableViewController.swift index 1247e4393..48f4eac04 100644 --- a/iOS/Settings/TimelinePreviewTableViewController.swift +++ b/iOS/Settings/TimelinePreviewTableViewController.swift @@ -8,6 +8,7 @@ import UIKit import Articles +import Images class TimelinePreviewTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { diff --git a/iOS/Timeline/Cell/TimelineCellData.swift b/iOS/Timeline/Cell/TimelineCellData.swift index 84c5c1c27..876f43972 100644 --- a/iOS/Timeline/Cell/TimelineCellData.swift +++ b/iOS/Timeline/Cell/TimelineCellData.swift @@ -8,6 +8,7 @@ import UIKit import Articles +import Images @MainActor struct TimelineCellData { diff --git a/iOS/Timeline/Cell/TimelineCellLayout.swift b/iOS/Timeline/Cell/TimelineCellLayout.swift index e86c673df..0e4ed37dd 100644 --- a/iOS/Timeline/Cell/TimelineCellLayout.swift +++ b/iOS/Timeline/Cell/TimelineCellLayout.swift @@ -7,6 +7,7 @@ // import UIKit +import Images protocol TimelineCellLayout { diff --git a/iOS/Timeline/Cell/TimelineTableViewCell.swift b/iOS/Timeline/Cell/TimelineTableViewCell.swift index 1b5127a61..020ec47b1 100644 --- a/iOS/Timeline/Cell/TimelineTableViewCell.swift +++ b/iOS/Timeline/Cell/TimelineTableViewCell.swift @@ -7,6 +7,7 @@ // import UIKit +import Images class TimelineTableViewCell: VibrantTableViewCell { diff --git a/iOS/Timeline/TimelineViewController.swift b/iOS/Timeline/TimelineViewController.swift index 434da9c52..5a1376845 100644 --- a/iOS/Timeline/TimelineViewController.swift +++ b/iOS/Timeline/TimelineViewController.swift @@ -10,6 +10,7 @@ import UIKit import Account import Articles import Core +import Images class TimelineViewController: UITableViewController, UndoableCommandRunner { @@ -446,7 +447,7 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner { titleView.iconView.iconImage = coordinator.timelineIconImage } - guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed else { + guard let feed = note.userInfo?[FeedIconDownloader.feedKey] as? Feed else { return }