center smaller avatars on a solid color background image to make all avatars a consistent size

This commit is contained in:
Maurice Parker 2019-06-14 15:33:13 -05:00
parent 46bc867241
commit 40b9be6709
16 changed files with 199 additions and 18 deletions

View File

@ -37,5 +37,12 @@ struct AppAssets {
static var faviconTemplateImage: RSImage = {
return RSImage(named: "faviconTemplateImage")!
}()
static var avatarBackgroundColor: NSColor = {
return NSColor(named: NSColor.Name("avatarBackgroundColor"))!
}()
static var avatarDarkBackgroundColor: NSColor = {
return NSColor(named: NSColor.Name("avatarDarkBackgroundColor"))!
}()
}

View File

@ -11,6 +11,10 @@ import RSCore
import Articles
import Account
extension Notification.Name {
static let AppleInterfaceThemeChangedNotification = Notification.Name("AppleInterfaceThemeChangedNotification")
}
protocol TimelineDelegate: class {
func timelineSelectionDidChange(_: TimelineViewController, selectedArticles: [Article]?)
}
@ -148,6 +152,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner {
NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .AccountsDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(calendarDayChanged(_:)), name: .NSCalendarDayChanged, object: nil)
DistributedNotificationCenter.default.addObserver(self, selector: #selector(appleInterfaceThemeChanged), name: .AppleInterfaceThemeChangedNotification, object: nil)
didRegisterForNotifications = true
}
@ -521,6 +526,15 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner {
}
}
@objc func appleInterfaceThemeChanged(_ note: Notification) {
appDelegate.authorAvatarDownloader.resetCache()
appDelegate.feedIconDownloader.resetCache()
appDelegate.faviconDownloader.resetCache()
performBlockAndRestoreSelection {
tableView.reloadData()
}
}
// MARK: - Reloading Data
private func cellForRowView(_ rowView: NSView) -> NSView? {
@ -752,7 +766,7 @@ extension TimelineViewController: NSTableViewDelegate {
return feedIcon
}
if let favicon = appDelegate.faviconDownloader.favicon(for: feed) {
if let favicon = appDelegate.faviconDownloader.faviconAsAvatar(for: feed) {
return favicon
}

View File

@ -0,0 +1,20 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "56",
"alpha" : "1.000",
"blue" : "56",
"green" : "56"
}
}
}
]
}

View File

@ -0,0 +1,20 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "242",
"alpha" : "1.000",
"blue" : "242",
"green" : "242"
}
}
}
]
}

View File

@ -2282,7 +2282,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# See https://github.com/Watson1978/kotori/commit/ffe320f2e058828f0af294b65ed88dfd7baaabff\n\nif [ \"${CONFIGURATION}\" = \"Release\" ]; then\n codesign --verbose --force --deep -o runtime --sign \"Developer ID Application: Brent Simmons\" \"${CODESIGNING_FOLDER_PATH}/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/AutoUpdate.app\"\nfi\n";
shellScript = "# See https://github.com/Watson1978/kotori/commit/ffe320f2e058828f0af294b65ed88dfd7baaabff\n\nif [ \"${CONFIGURATION}\" = \"Release\" ]; then\n codesign --verbose --force --deep -o runtime --sign \"Developer ID Application: Vincode, Inc\" \"${CODESIGNING_FOLDER_PATH}/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/AutoUpdate.app\"\nfi\n";
};
84C987A52000AC9E0066B150 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
@ -2823,7 +2823,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = iOS/Resources/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
@ -2886,7 +2886,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = iOS/Resources/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = "com.ranchero.NetNewsWire-Evergreen.iOS";

View File

@ -210,11 +210,7 @@ private extension ArticleRenderer {
}
func base64String(forImage image: RSImage) -> String? {
#if os(macOS)
return image.tiffRepresentation?.base64EncodedString()
#else
return image.pngData()?.base64EncodedString()
#endif
return image.dataRepresentation()?.base64EncodedString()
}
func singleArticleSpecifiedAuthor() -> Author? {

View File

@ -21,16 +21,17 @@ extension RSImage {
}
}
}
}
private extension RSImage {
static func scaledForAvatar(_ data: Data) -> RSImage? {
let scaledMaxPixelSize = Int(ceil(CGFloat(RSImage.avatarSize) * RSScreen.mainScreenScale))
guard let cgImage = RSImage.scaleImage(data, maxPixelSize: scaledMaxPixelSize) else {
guard var cgImage = RSImage.scaleImage(data, maxPixelSize: scaledMaxPixelSize) else {
return nil
}
if cgImage.width < avatarSize || cgImage.height < avatarSize {
cgImage = RSImage.compositeAvatar(cgImage)
}
#if os(iOS)
return RSImage(cgImage: cgImage)
#else
@ -41,3 +42,49 @@ private extension RSImage {
}
}
private extension RSImage {
#if os(iOS)
static func compositeAvatar(_ avatar: CGImage) -> CGImage {
let rect = CGRect(x: 0, y: 0, width: avatarSize, height: avatarSize)
UIGraphicsBeginImageContext(rect.size)
if let context = UIGraphicsGetCurrentContext() {
context.setFillColor(AppAssets.avatarLightBackgroundColor.cgColor)
context.fill(rect)
context.translateBy(x: 0.0, y: CGFloat(integerLiteral: avatarSize));
context.scaleBy(x: 1.0, y: -1.0)
let avatarRect = CGRect(x: (avatarSize - avatar.width) / 2, y: (avatarSize - avatar.height) / 2, width: avatar.width, height: avatar.height)
context.draw(avatar, in: avatarRect)
}
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img!.cgImage!
}
#else
static func compositeAvatar(_ avatar: CGImage) -> CGImage {
var resultRect = CGRect(x: 0, y: 0, width: avatarSize, height: avatarSize)
let resultImage = NSImage(size: resultRect.size)
resultImage.lockFocus()
if let context = NSGraphicsContext.current?.cgContext {
if NSApplication.shared.effectiveAppearance.isDarkMode {
context.setFillColor(AppAssets.avatarDarkBackgroundColor.cgColor)
} else {
context.setFillColor(AppAssets.avatarLightBackgroundColor.cgColor)
}
context.fill(resultRect)
let avatarRect = CGRect(x: (avatarSize - avatar.width) / 2, y: (avatarSize - avatar.height) / 2, width: avatar.width, height: avatar.height)
context.draw(avatar, in: avatarRect)
}
resultImage.unlockFocus()
return resultImage.cgImage(forProposedRect: &resultRect, context: nil, hints: nil)!
}
#endif
}

View File

@ -24,6 +24,7 @@ final class FaviconDownloader {
private var homePageToFaviconURLCache = [String: String]() //homePageURL: faviconURL
private var homePageURLsWithNoFaviconURL = Set<String>()
private let queue: DispatchQueue
private var cache = [Feed: RSImage]() // faviconURL: RSImage
struct UserInfoKey {
static let faviconURL = "faviconURL"
@ -40,6 +41,10 @@ final class FaviconDownloader {
// MARK: - API
func resetCache() {
cache = [Feed: RSImage]()
}
func favicon(for feed: Feed) -> RSImage? {
assert(Thread.isMainThread)
@ -61,6 +66,22 @@ final class FaviconDownloader {
return nil
}
func faviconAsAvatar(for feed: Feed) -> RSImage? {
if let image = cache[feed] {
return image
}
if let image = favicon(for: feed), let imageData = image.dataRepresentation() {
if let scaledImage = RSImage.scaledForAvatar(imageData) {
cache[feed] = scaledImage
return scaledImage
}
}
return nil
}
func favicon(with faviconURL: String) -> RSImage? {

View File

@ -99,7 +99,7 @@ private extension SingleFaviconDownloader {
queue.async {
if let data = self.diskCache[self.diskKey], !data.isEmpty {
RSImage.scaledForAvatar(data, imageResultBlock: callback)
RSImage.rs_image(with: data, imageResultBlock: callback)
return
}
@ -134,7 +134,7 @@ private extension SingleFaviconDownloader {
if let data = data, !data.isEmpty, let response = response, response.statusIsOK, error == nil {
self.saveToDisk(data)
RSImage.scaledForAvatar(data, imageResultBlock: callback)
RSImage.rs_image(with: data, imageResultBlock: callback)
return
}

View File

@ -27,6 +27,10 @@ final class AuthorAvatarDownloader {
NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .ImageDidBecomeAvailable, object: imageDownloader)
}
func resetCache() {
cache = [String: RSImage]()
}
func image(for author: Author) -> RSImage? {
guard let avatarURL = author.avatarURL else {

View File

@ -31,6 +31,10 @@ public final class FeedIconDownloader {
self.imageDownloader = imageDownloader
}
func resetCache() {
cache = [Feed: RSImage]()
}
func icon(for feed: Feed) -> RSImage? {
if let cachedImage = cache[feed] {

View File

@ -10,6 +10,14 @@ import RSCore
struct AppAssets {
static var avatarDarkBackgroundColor: UIColor {
return UIColor(named: "avatarDarkBackgroundColor")!
}
static var avatarLightBackgroundColor: UIColor {
return UIColor(named: "avatarLightBackgroundColor")!
}
static var circleClosedImage: RSImage = {
return RSImage(named: "circleClosedImage")!
}()

View File

@ -407,7 +407,7 @@ private extension MasterTimelineViewController {
return feedIconImage
}
if let feed = article.feed, let faviconImage = appDelegate.faviconDownloader.favicon(for: feed) {
if let feed = article.feed, let faviconImage = appDelegate.faviconDownloader.faviconAsAvatar(for: feed) {
return faviconImage
}

View File

@ -0,0 +1,20 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "56",
"alpha" : "1.000",
"blue" : "56",
"green" : "56"
}
}
}
]
}

View File

@ -0,0 +1,20 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "242",
"alpha" : "1.000",
"blue" : "242",
"green" : "242"
}
}
}
]
}

@ -1 +1 @@
Subproject commit aa7107080e90d5be11ae54fd41ee4dd192468e30
Subproject commit 111690033354afc1cf57e37a326c344a0fe93b77