mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-02-01 03:26:54 +01:00
Made thumbnails of all images used as an avatar and made the favicon and images cross platform. Issue #603
This commit is contained in:
parent
630db70020
commit
e34f8c8b5e
@ -7,6 +7,8 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; };
|
||||
51126DA5225FDE2F00722696 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; };
|
||||
5127B238222B4849006D641D /* DetailKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */; };
|
||||
5127B239222B4849006D641D /* DetailKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */; };
|
||||
5127B23A222B4849006D641D /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; };
|
||||
@ -747,6 +749,7 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSImage-Extensions.swift"; sourceTree = "<group>"; };
|
||||
5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailKeyboardDelegate.swift; sourceTree = "<group>"; };
|
||||
5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = DetailKeyboardShortcuts.plist; sourceTree = "<group>"; };
|
||||
519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = "<group>"; };
|
||||
@ -1457,6 +1460,7 @@
|
||||
children = (
|
||||
849A97971ED9EFAA007D329B /* Node-Extensions.swift */,
|
||||
8405DD9B22153BD7008CE1BF /* NSView-Extensions.swift */,
|
||||
51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */,
|
||||
);
|
||||
name = Extensions;
|
||||
path = NetNewsWire/Extensions;
|
||||
@ -2497,6 +2501,7 @@
|
||||
840F7C4A21BDA4B40057E851 /* RSHTMLMetadata+Extension.swift in Sources */,
|
||||
840F7C4B21BDA4B40057E851 /* SendToMarsEditCommand.swift in Sources */,
|
||||
842AE5BC2241F37B004A742C /* AccountsPreferencesViewController.swift in Sources */,
|
||||
51126DA5225FDE2F00722696 /* RSImage-Extensions.swift in Sources */,
|
||||
840F7C4C21BDA4B40057E851 /* ScriptingObjectContainer.swift in Sources */,
|
||||
840F7C4D21BDA4B40057E851 /* ArticleStylesManager.swift in Sources */,
|
||||
840F7C4E21BDA4B40057E851 /* SharingServiceDelegate.swift in Sources */,
|
||||
@ -2682,6 +2687,7 @@
|
||||
844B5B591FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift in Sources */,
|
||||
51EC114C2149FE3300B296E3 /* FolderTreeMenu.swift in Sources */,
|
||||
849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */,
|
||||
51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */,
|
||||
84A1500320048D660046AD9A /* SendToCommand.swift in Sources */,
|
||||
845A29091FC74B8E007B49E3 /* SingleFaviconDownloader.swift in Sources */,
|
||||
849A97851ED9ECCD007D329B /* PreferencesWindowController.swift in Sources */,
|
||||
|
41
NetNewsWire/Extensions/RSImage-Extensions.swift
Normal file
41
NetNewsWire/Extensions/RSImage-Extensions.swift
Normal file
@ -0,0 +1,41 @@
|
||||
//
|
||||
// RSImage-Extensions.swift
|
||||
// RSCore
|
||||
//
|
||||
// Created by Maurice Parker on 4/11/19.
|
||||
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RSCore
|
||||
|
||||
extension RSImage {
|
||||
|
||||
static let avatarSize = 48
|
||||
|
||||
static func scaledForAvatar(_ data: Data, imageResultBlock: @escaping (RSImage?) -> Void) {
|
||||
DispatchQueue.global().async {
|
||||
let image = RSImage.scaledForAvatar(data)
|
||||
DispatchQueue.main.async {
|
||||
imageResultBlock(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func scaledForAvatar(_ data: Data) -> RSImage? {
|
||||
|
||||
let scaledMaxPixelSize = Int(ceil(CGFloat(RSImage.avatarSize) * RSScreen.mainScreenScale))
|
||||
guard let cgImage = RSImage.scaleImage(data, maxPixelSize: scaledMaxPixelSize) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
return RSImage(cgImage: cgImage)
|
||||
#else
|
||||
let size = NSSize(width: scaledMaxPixelSize, height: scaledMaxPixelSize)
|
||||
return RSImage(cgImage: cgImage, size: size)
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
// Copyright © 2017 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Foundation
|
||||
import Articles
|
||||
import Account
|
||||
import RSCore
|
||||
@ -40,7 +40,7 @@ final class FaviconDownloader {
|
||||
|
||||
// MARK: - API
|
||||
|
||||
func favicon(for feed: Feed) -> NSImage? {
|
||||
func favicon(for feed: Feed) -> RSImage? {
|
||||
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
@ -62,13 +62,13 @@ final class FaviconDownloader {
|
||||
return nil
|
||||
}
|
||||
|
||||
func favicon(with faviconURL: String) -> NSImage? {
|
||||
func favicon(with faviconURL: String) -> RSImage? {
|
||||
|
||||
let downloader = faviconDownloader(withURL: faviconURL)
|
||||
return downloader.image
|
||||
}
|
||||
|
||||
func favicon(withHomePageURL homePageURL: String) -> NSImage? {
|
||||
func favicon(withHomePageURL homePageURL: String) -> RSImage? {
|
||||
|
||||
let url = homePageURL.rs_normalizedURL()
|
||||
if homePageURLsWithNoFaviconURL.contains(url) {
|
||||
|
@ -25,7 +25,7 @@ final class SingleFaviconDownloader {
|
||||
}
|
||||
|
||||
let faviconURL: String
|
||||
var image: NSImage?
|
||||
var image: RSImage?
|
||||
|
||||
private var lastDownloadAttemptDate: Date
|
||||
private var diskStatus = DiskStatus.unknown
|
||||
@ -89,7 +89,7 @@ private extension SingleFaviconDownloader {
|
||||
}
|
||||
}
|
||||
|
||||
func readFromDisk(_ callback: @escaping (NSImage?) -> Void) {
|
||||
func readFromDisk(_ callback: @escaping (RSImage?) -> Void) {
|
||||
|
||||
guard diskStatus != .notOnDisk else {
|
||||
callback(nil)
|
||||
@ -99,7 +99,7 @@ private extension SingleFaviconDownloader {
|
||||
queue.async {
|
||||
|
||||
if let data = self.diskCache[self.diskKey], !data.isEmpty {
|
||||
NSImage.rs_image(with: data, imageResultBlock: callback)
|
||||
RSImage.scaledForAvatar(data, imageResultBlock: callback)
|
||||
return
|
||||
}
|
||||
|
||||
@ -123,7 +123,7 @@ private extension SingleFaviconDownloader {
|
||||
}
|
||||
}
|
||||
|
||||
func downloadFavicon(_ callback: @escaping (NSImage?) -> Void) {
|
||||
func downloadFavicon(_ callback: @escaping (RSImage?) -> Void) {
|
||||
|
||||
guard let url = URL(string: faviconURL) else {
|
||||
callback(nil)
|
||||
@ -134,7 +134,7 @@ private extension SingleFaviconDownloader {
|
||||
|
||||
if let data = data, !data.isEmpty, let response = response, response.statusIsOK, error == nil {
|
||||
self.saveToDisk(data)
|
||||
NSImage.rs_image(with: data, imageResultBlock: callback)
|
||||
RSImage.scaledForAvatar(data, imageResultBlock: callback)
|
||||
return
|
||||
}
|
||||
|
||||
@ -151,4 +151,5 @@ private extension SingleFaviconDownloader {
|
||||
assert(Thread.isMainThread)
|
||||
NotificationCenter.default.post(name: .DidLoadFavicon, object: self)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,8 +6,9 @@
|
||||
// Copyright © 2017 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Foundation
|
||||
import Articles
|
||||
import RSCore
|
||||
|
||||
extension Notification.Name {
|
||||
|
||||
@ -17,7 +18,7 @@ extension Notification.Name {
|
||||
final class AuthorAvatarDownloader {
|
||||
|
||||
private let imageDownloader: ImageDownloader
|
||||
private var cache = [String: NSImage]() // avatarURL: NSImage
|
||||
private var cache = [String: RSImage]() // avatarURL: RSImage
|
||||
private var waitingForAvatarURLs = Set<String>()
|
||||
|
||||
init(imageDownloader: ImageDownloader) {
|
||||
@ -26,19 +27,22 @@ final class AuthorAvatarDownloader {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .ImageDidBecomeAvailable, object: imageDownloader)
|
||||
}
|
||||
|
||||
func image(for author: Author) -> NSImage? {
|
||||
func image(for author: Author) -> RSImage? {
|
||||
|
||||
guard let avatarURL = author.avatarURL else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let cachedImage = cache[avatarURL] {
|
||||
return cachedImage
|
||||
}
|
||||
if let image = imageDownloader.image(for: avatarURL) {
|
||||
handleImageDidBecomeAvailable(avatarURL, image)
|
||||
return image
|
||||
}
|
||||
else {
|
||||
|
||||
if let imageData = imageDownloader.image(for: avatarURL) {
|
||||
if let image = RSImage.scaledForAvatar(imageData) {
|
||||
handleImageDidBecomeAvailable(avatarURL, image)
|
||||
return image
|
||||
}
|
||||
} else {
|
||||
waitingForAvatarURLs.insert(avatarURL)
|
||||
}
|
||||
|
||||
@ -50,20 +54,24 @@ final class AuthorAvatarDownloader {
|
||||
guard let avatarURL = note.userInfo?[UserInfoKey.url] as? String else {
|
||||
return
|
||||
}
|
||||
|
||||
guard waitingForAvatarURLs.contains(avatarURL) else {
|
||||
return
|
||||
}
|
||||
guard let image = imageDownloader.image(for: avatarURL) else {
|
||||
|
||||
guard let imageData = imageDownloader.image(for: avatarURL),
|
||||
let image = RSImage.scaledForAvatar(imageData) else {
|
||||
return
|
||||
}
|
||||
|
||||
handleImageDidBecomeAvailable(avatarURL, image)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private extension AuthorAvatarDownloader {
|
||||
|
||||
func handleImageDidBecomeAvailable(_ avatarURL: String, _ image: NSImage) {
|
||||
func handleImageDidBecomeAvailable(_ avatarURL: String, _ image: RSImage) {
|
||||
|
||||
if cache[avatarURL] == nil {
|
||||
cache[avatarURL] = image
|
||||
|
@ -6,8 +6,9 @@
|
||||
// Copyright © 2017 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Foundation
|
||||
import Articles
|
||||
import RSCore
|
||||
import RSParser
|
||||
|
||||
final class FeaturedImageDownloader {
|
||||
@ -22,7 +23,7 @@ final class FeaturedImageDownloader {
|
||||
self.imageDownloader = imageDownloader
|
||||
}
|
||||
|
||||
func image(for article: Article) -> NSImage? {
|
||||
func image(for article: Article) -> RSImage? {
|
||||
|
||||
if let url = article.imageURL {
|
||||
return image(forFeaturedImageURL: url)
|
||||
@ -33,7 +34,7 @@ final class FeaturedImageDownloader {
|
||||
return nil
|
||||
}
|
||||
|
||||
func image(forArticleURL articleURL: String) -> NSImage? {
|
||||
func image(forArticleURL articleURL: String) -> RSImage? {
|
||||
|
||||
if articleURLsWithNoFeaturedImage.contains(articleURL) {
|
||||
return nil
|
||||
@ -46,10 +47,13 @@ final class FeaturedImageDownloader {
|
||||
return nil
|
||||
}
|
||||
|
||||
func image(forFeaturedImageURL featuredImageURL: String) -> NSImage? {
|
||||
|
||||
return imageDownloader.image(for: featuredImageURL)
|
||||
func image(forFeaturedImageURL featuredImageURL: String) -> RSImage? {
|
||||
if let data = imageDownloader.image(for: featuredImageURL) {
|
||||
return RSImage(data: data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension FeaturedImageDownloader {
|
||||
|
@ -9,6 +9,7 @@
|
||||
import AppKit
|
||||
import Articles
|
||||
import Account
|
||||
import RSCore
|
||||
import RSWeb
|
||||
import RSParser
|
||||
|
||||
@ -23,14 +24,14 @@ public final class FeedIconDownloader {
|
||||
private var homePageToIconURLCache = [String: String]()
|
||||
private var homePagesWithNoIconURL = Set<String>()
|
||||
private var urlsInProgress = Set<String>()
|
||||
private var cache = [Feed: NSImage]()
|
||||
private var cache = [Feed: RSImage]()
|
||||
|
||||
init(imageDownloader: ImageDownloader) {
|
||||
|
||||
self.imageDownloader = imageDownloader
|
||||
}
|
||||
|
||||
func icon(for feed: Feed) -> NSImage? {
|
||||
func icon(for feed: Feed) -> RSImage? {
|
||||
|
||||
if let cachedImage = cache[feed] {
|
||||
return cachedImage
|
||||
@ -58,7 +59,7 @@ public final class FeedIconDownloader {
|
||||
|
||||
private extension FeedIconDownloader {
|
||||
|
||||
func icon(forHomePageURL homePageURL: String) -> NSImage? {
|
||||
func icon(forHomePageURL homePageURL: String) -> RSImage? {
|
||||
|
||||
if homePagesWithNoIconURL.contains(homePageURL) {
|
||||
return nil
|
||||
@ -72,9 +73,11 @@ private extension FeedIconDownloader {
|
||||
return nil
|
||||
}
|
||||
|
||||
func icon(forURL url: String) -> NSImage? {
|
||||
|
||||
return imageDownloader.image(for: url)
|
||||
func icon(forURL url: String) -> RSImage? {
|
||||
if let imageData = imageDownloader.image(for: url), let image = RSImage.scaledForAvatar(imageData) {
|
||||
return image
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func postFeedIconDidBecomeAvailableNotification(_ feed: Feed) {
|
||||
|
@ -6,7 +6,7 @@
|
||||
// Copyright © 2017 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Foundation
|
||||
import RSCore
|
||||
import RSWeb
|
||||
|
||||
@ -20,7 +20,7 @@ final class ImageDownloader {
|
||||
private let folder: String
|
||||
private var diskCache: BinaryDiskCache
|
||||
private let queue: DispatchQueue
|
||||
private var imageCache = [String: NSImage]() // url: image
|
||||
private var imageCache = [String: Data]() // url: image
|
||||
private var urlsInProgress = Set<String>()
|
||||
private var badURLs = Set<String>() // That return a 404 or whatever. Just skip them in the future.
|
||||
|
||||
@ -32,10 +32,10 @@ final class ImageDownloader {
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func image(for url: String) -> NSImage? {
|
||||
func image(for url: String) -> Data? {
|
||||
|
||||
if let image = imageCache[url] {
|
||||
return image
|
||||
if let data = imageCache[url] {
|
||||
return data
|
||||
}
|
||||
|
||||
findImage(url)
|
||||
@ -45,7 +45,7 @@ final class ImageDownloader {
|
||||
|
||||
private extension ImageDownloader {
|
||||
|
||||
func cacheImage(_ url: String, _ image: NSImage) {
|
||||
func cacheImage(_ url: String, _ image: Data) {
|
||||
|
||||
imageCache[url] = image
|
||||
postImageDidBecomeAvailableNotification(url)
|
||||
@ -76,12 +76,14 @@ private extension ImageDownloader {
|
||||
}
|
||||
}
|
||||
|
||||
func readFromDisk(_ url: String, _ callback: @escaping (NSImage?) -> Void) {
|
||||
func readFromDisk(_ url: String, _ callback: @escaping (Data?) -> Void) {
|
||||
|
||||
queue.async {
|
||||
|
||||
if let data = self.diskCache[self.diskKey(url)], !data.isEmpty {
|
||||
NSImage.rs_image(with: data, imageResultBlock: callback)
|
||||
DispatchQueue.main.async {
|
||||
callback(data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -91,7 +93,7 @@ private extension ImageDownloader {
|
||||
}
|
||||
}
|
||||
|
||||
func downloadImage(_ url: String, _ callback: @escaping (NSImage?) -> Void) {
|
||||
func downloadImage(_ url: String, _ callback: @escaping (Data?) -> Void) {
|
||||
|
||||
guard let imageURL = URL(string: url) else {
|
||||
callback(nil)
|
||||
@ -102,7 +104,7 @@ private extension ImageDownloader {
|
||||
|
||||
if let data = data, !data.isEmpty, let response = response, response.statusIsOK, error == nil {
|
||||
self.saveToDisk(url, data)
|
||||
NSImage.rs_image(with: data, imageResultBlock: callback)
|
||||
callback(data)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -692,13 +692,16 @@ extension TimelineViewController: NSTableViewDelegate {
|
||||
private func featuredImageFor(_ article: Article) -> NSImage? {
|
||||
|
||||
if let url = article.imageURL {
|
||||
return appDelegate.imageDownloader.image(for: url)
|
||||
if let imageData = appDelegate.imageDownloader.image(for: url) {
|
||||
return NSImage(data: imageData)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
private func makeTimelineCellEmpty(_ cell: TimelineTableCellView) {
|
||||
|
||||
cell.objectValue = nil
|
||||
cell.cellData = TimelineCellData()
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user