diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 1cc18e77a..898bb9eb0 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -106,6 +106,9 @@ 51C452B82265178500C03939 /* styleSheet.css in Resources */ = {isa = PBXBuildFile; fileRef = 51C452B72265178500C03939 /* styleSheet.css */; }; 51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; }; 51EC114C2149FE3300B296E3 /* FolderTreeMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */; }; + 51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; }; + 51EF0F79227716380050506E /* ColorHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F78227716380050506E /* ColorHash.swift */; }; + 51EF0F7A22771B890050506E /* ColorHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F78227716380050506E /* ColorHash.swift */; }; 51F85BE5227217D000C787DC /* RefreshIntervalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BDB2272162F00C787DC /* RefreshIntervalViewController.swift */; }; 51F85BE7227245FC00C787DC /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BE6227245FC00C787DC /* AboutViewController.swift */; }; 51F85BEB22724CB600C787DC /* About.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 51F85BEA22724CB600C787DC /* About.rtf */; }; @@ -662,6 +665,8 @@ 51C452B32265141B00C03939 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; }; 51C452B72265178500C03939 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; 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 = ""; }; + 51EF0F78227716380050506E /* ColorHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorHash.swift; sourceTree = ""; }; 51F85BDB2272162F00C787DC /* RefreshIntervalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshIntervalViewController.swift; sourceTree = ""; }; 51F85BE6227245FC00C787DC /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 51F85BEA22724CB600C787DC /* About.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = About.rtf; sourceTree = ""; }; @@ -1241,9 +1246,11 @@ 848F6AE31FC29CFA002D422E /* Favicons */ = { isa = PBXGroup; children = ( + 51EF0F78227716380050506E /* ColorHash.swift */, 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */, - 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */, + 51EF0F76227716200050506E /* FaviconGenerator.swift */, 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */, + 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */, ); path = Favicons; sourceTree = ""; @@ -2227,6 +2234,7 @@ 840D617F2029031C009BC708 /* AppDelegate.swift in Sources */, 512E08E72268801200BDCFDD /* FeedTreeControllerDelegate.swift in Sources */, 51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */, + 51EF0F79227716380050506E /* ColorHash.swift in Sources */, 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */, 51C4527B2265091600C03939 /* MasterUnreadIndicatorView.swift in Sources */, 51F85BF92274AA7B00C787DC /* UIBarButtonItem-Extensions.swift in Sources */, @@ -2248,6 +2256,7 @@ 51C452852265093600C03939 /* AddFeedFolderPickerData.swift in Sources */, 51C4526B226508F600C03939 /* MasterFeedViewController.swift in Sources */, 5126EE97226CB48A00C22AFC /* NavigationStateController.swift in Sources */, + 51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */, 51C4525A226508D600C03939 /* UIStoryboard-Extensions.swift in Sources */, 5183CCEF227125970010922C /* SettingsViewController.swift in Sources */, 51F85BE5227217D000C787DC /* RefreshIntervalViewController.swift in Sources */, @@ -2318,6 +2327,7 @@ 84F204E01FAACBB30076E152 /* ArticleArray.swift in Sources */, 848B937221C8C5540038DC0D /* CrashReporter.swift in Sources */, 84BBB12E20142A4700F054F5 /* InspectorWindowController.swift in Sources */, + 51EF0F7A22771B890050506E /* ColorHash.swift in Sources */, 84E46C7D1F75EF7B005ECFB3 /* AppDefaults.swift in Sources */, D5907D972004B7EB005947E5 /* Account+Scriptability.swift in Sources */, 841ABA4E20145E7300980E11 /* NothingInspectorViewController.swift in Sources */, diff --git a/Shared/Data/SmallIconProvider.swift b/Shared/Data/SmallIconProvider.swift index a3db9d5be..2f3c366c7 100644 --- a/Shared/Data/SmallIconProvider.swift +++ b/Shared/Data/SmallIconProvider.swift @@ -25,7 +25,7 @@ extension Feed: SmallIconProvider { #if os(macOS) return AppImages.genericFeedImage #else - return AppAssets.feedImage + return FaviconGenerator.favicon(self) #endif } } @@ -39,4 +39,5 @@ extension Folder: SmallIconProvider { return AppAssets.masterFolderImage #endif } + } diff --git a/Shared/Favicons/ColorHash.swift b/Shared/Favicons/ColorHash.swift new file mode 100644 index 000000000..8849a0fe3 --- /dev/null +++ b/Shared/Favicons/ColorHash.swift @@ -0,0 +1,72 @@ +// +// ColorHash.swift +// ColorHash +// +// Created by Atsushi Nagase on 11/25/15. +// Copyright © 2015 LittleApps Inc. All rights reserved. +// +// Original Project: https://github.com/ngs/color-hash.swift + +import Foundation +#if os(iOS) || os(tvOS) +import UIKit +#elseif os(watchOS) +import WatchKit +#elseif os(OSX) +import Cocoa +#endif + +public class ColorHash { + + public static let defaultLS = [CGFloat(0.35), CGFloat(0.5), CGFloat(0.65)] + let seed = CGFloat(131.0) + let seed2 = CGFloat(137.0) + let maxSafeInteger = 9007199254740991.0 / CGFloat(137.0) + let full = CGFloat(360.0) + + public private(set) var str: String + public private(set) var brightness: [CGFloat] + public private(set) var saturation: [CGFloat] + + public init(_ str: String, _ saturation: [CGFloat] = defaultLS, _ brightness: [CGFloat] = defaultLS) { + self.str = str + self.saturation = saturation + self.brightness = brightness + } + + public var bkdrHash: CGFloat { + var hash = CGFloat(0) + for char in "\(str)x" { + if let scl = String(char).unicodeScalars.first?.value { + if hash > maxSafeInteger { + hash = hash / seed2 + } + hash = hash * seed + CGFloat(scl) + } + } + return hash + } + + public var HSB: (CGFloat, CGFloat, CGFloat) { + var hash = CGFloat(bkdrHash) + let H = hash.truncatingRemainder(dividingBy: (full - 1.0)) / full + hash /= full + let S = saturation[Int((full * hash).truncatingRemainder(dividingBy: CGFloat(saturation.count)))] + hash /= CGFloat(saturation.count) + let B = brightness[Int((full * hash).truncatingRemainder(dividingBy: CGFloat(brightness.count)))] + return (H, S, B) + } + + #if os(iOS) || os(tvOS) || os(watchOS) + public var color: UIColor { + let (H, S, B) = HSB + return UIColor(hue: H, saturation: S, brightness: B, alpha: 1.0) + } + #elseif os(OSX) + public var color: NSColor { + let (H, S, B) = HSB + return NSColor(hue: H, saturation: S, brightness: B, alpha: 1.0) + } + #endif + +} diff --git a/Shared/Favicons/FaviconGenerator.swift b/Shared/Favicons/FaviconGenerator.swift new file mode 100644 index 000000000..55c0daa09 --- /dev/null +++ b/Shared/Favicons/FaviconGenerator.swift @@ -0,0 +1,33 @@ +// +// FaviconGenerator.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 4/29/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import Foundation +import RSCore +import Account + +final class FaviconGenerator { + + private static var faviconGeneratorCache = [String: RSImage]() // feedURL: RSImage + + static func favicon(_ feed: Feed) -> RSImage { + + if let favicon = FaviconGenerator.faviconGeneratorCache[feed.url] { + return favicon + } + + let colorHash = ColorHash(feed.url) + if let favicon = AppAssets.faviconTemplateImage.maskWithColor(color: colorHash.color) { + FaviconGenerator.faviconGeneratorCache[feed.url] = favicon + return favicon + } else { + return AppAssets.faviconTemplateImage + } + + } + +} diff --git a/iOS/AppAssets.swift b/iOS/AppAssets.swift index 2edcde6ee..d67309528 100644 --- a/iOS/AppAssets.swift +++ b/iOS/AppAssets.swift @@ -32,6 +32,10 @@ struct AppAssets { return image.maskWithColor(color: AppAssets.chevronDisclosureColor)! }() + static var faviconTemplateImage: RSImage = { + return RSImage(named: "faviconTemplateImage")! + }() + static var feedColor: UIColor = { return UIColor(named: "feedColor")! }() diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift index 5070e23df..6df870d96 100644 --- a/iOS/MasterTimeline/MasterTimelineViewController.swift +++ b/iOS/MasterTimeline/MasterTimelineViewController.swift @@ -404,7 +404,7 @@ private extension MasterTimelineViewController { } } - return nil + return FaviconGenerator.favicon(feed) } diff --git a/iOS/Resources/Assets.xcassets/faviconTemplateImage.imageset/Contents.json b/iOS/Resources/Assets.xcassets/faviconTemplateImage.imageset/Contents.json new file mode 100644 index 000000000..5a1ae2f6a --- /dev/null +++ b/iOS/Resources/Assets.xcassets/faviconTemplateImage.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "faviconTemplateImage.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/iOS/Resources/Assets.xcassets/faviconTemplateImage.imageset/faviconTemplateImage.pdf b/iOS/Resources/Assets.xcassets/faviconTemplateImage.imageset/faviconTemplateImage.pdf new file mode 100644 index 000000000..d6bcb5b69 Binary files /dev/null and b/iOS/Resources/Assets.xcassets/faviconTemplateImage.imageset/faviconTemplateImage.pdf differ