center smaller avatars on a solid color background image to make all avatars a consistent size
This commit is contained in:
parent
46bc867241
commit
40b9be6709
|
@ -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"))!
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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? {
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
@ -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? {
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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] {
|
||||
|
|
|
@ -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")!
|
||||
}()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue