Made thumbnails of all images used as an avatar and made the favicon and images cross platform. Issue #603

This commit is contained in:
Maurice Parker 2019-04-11 17:53:03 -05:00
parent 630db70020
commit e34f8c8b5e
9 changed files with 111 additions and 43 deletions

View File

@ -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 */,

View 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
}
}

View File

@ -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) {

View File

@ -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)
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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) {

View File

@ -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
}

View File

@ -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()
}