diff --git a/Mac/AppAssets.swift b/Mac/AppAssets.swift index 5e4699fa1..b9f702e03 100644 --- a/Mac/AppAssets.swift +++ b/Mac/AppAssets.swift @@ -125,6 +125,10 @@ struct AppAssets { return IconImage(RSImage(named: NSImage.smartBadgeTemplateName)!) }() + static var smartFeedImage: RSImage = { + return RSImage(named: NSImage.smartBadgeTemplateName)! + }() + static var starredFeedImage: IconImage = { return IconImage(RSImage(named: NSImage.smartBadgeTemplateName)!) }() diff --git a/Multiplatform/Shared/AppAssets.swift b/Multiplatform/Shared/AppAssets.swift new file mode 100644 index 000000000..37998ed7b --- /dev/null +++ b/Multiplatform/Shared/AppAssets.swift @@ -0,0 +1,153 @@ +// +// AppAssets.swift +// NetNewsWire +// +// Created by Maurice Parker on 6/27/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import SwiftUI +import RSCore +import Account + +struct AppAssets { + + static var accountLocalMacImage: RSImage! = { + return RSImage(named: "accountLocalMac") + }() + + static var accountLocalPadImage: RSImage = { + return RSImage(named: "accountLocalPad")! + }() + + static var accountLocalPhoneImage: RSImage = { + return RSImage(named: "accountLocalPhone")! + }() + + static var accountCloudKitImage: RSImage = { + return RSImage(named: "accountCloudKit")! + }() + + static var accountFeedbinImage: RSImage = { + return RSImage(named: "accountFeedbin")! + }() + + static var accountFeedlyImage: RSImage = { + return RSImage(named: "accountFeedly")! + }() + + static var accountFeedWranglerImage: RSImage = { + return RSImage(named: "accountFeedWrangler")! + }() + + static var accountFreshRSSImage: RSImage = { + return RSImage(named: "accountFreshRSS")! + }() + + static var accountNewsBlurImage: RSImage = { + return RSImage(named: "accountNewsBlur")! + }() + + static var extensionPointMarsEdit: RSImage = { + return RSImage(named: "extensionPointMarsEdit")! + }() + + static var extensionPointMicroblog: RSImage = { + return RSImage(named: "extensionPointMicroblog")! + }() + + static var extensionPointReddit: RSImage = { + return RSImage(named: "extensionPointReddit")! + }() + + static var extensionPointTwitter: RSImage = { + return RSImage(named: "extensionPointTwitter")! + }() + + static var faviconTemplateImage: RSImage = { + return RSImage(named: "faviconTemplateImage")! + }() + + static var masterFolderImage: IconImage = { + #if os(macOS) + return IconImage(NSImage(systemSymbolName: "folder.fill", accessibilityDescription: nil)!) + #endif + #if os(iOS) + return IconImage(UIImage(systemName: "folder.fill")!) + #endif + }() + + static var searchFeedImage: IconImage = { + #if os(macOS) + return IconImage(NSImage(systemSymbolName: "magnifyingglass", accessibilityDescription: nil)!) + #endif + #if os(iOS) + return IconImage(UIImage(systemName: "magnifyingglass")!) + #endif + }() + + static var smartFeedImage: RSImage = { + #if os(macOS) + return NSImage(systemSymbolName: "gear", accessibilityDescription: nil)! + #endif + #if os(iOS) + return UIImage(systemName: "gear")! + #endif + }() + + static var starredFeedImage: IconImage = { + #if os(macOS) + return IconImage(NSImage(systemSymbolName: "star.fill", accessibilityDescription: nil)!) + #endif + #if os(iOS) + return IconImage(UIImage(systemName: "star.fill")!) + #endif + }() + + static var todayFeedImage: IconImage = { + #if os(macOS) + return IconImage(NSImage(systemSymbolName: "sun.max.fill", accessibilityDescription: nil)!) + #endif + #if os(iOS) + return IconImage(UIImage(systemName: "sun.max.fill")!) + #endif + }() + + static var unreadFeedImage: IconImage = { + #if os(macOS) + return IconImage(NSImage(systemSymbolName: "largecircle.fill.circle", accessibilityDescription: nil)!) + #endif + #if os(iOS) + return IconImage(UIImage(systemName: "largecircle.fill.circle")!) + #endif + }() + + static func image(for accountType: AccountType) -> RSImage? { + switch accountType { + case .onMyMac: + #if os(macOS) + return AppAssets.accountLocalMacImage + #endif + #if os(iOS) + if UIDevice.current.userInterfaceIdiom == .pad { + return AppAssets.accountLocalPadImage + } else { + return AppAssets.accountLocalPhoneImage + } + #endif + case .cloudKit: + return AppAssets.accountCloudKitImage + case .feedbin: + return AppAssets.accountFeedbinImage + case .feedly: + return AppAssets.accountFeedlyImage + case .feedWrangler: + return AppAssets.accountFeedWranglerImage + case .freshRSS: + return AppAssets.accountFreshRSSImage + case .newsBlur: + return AppAssets.accountNewsBlurImage + } + } + +} diff --git a/Multiplatform/Shared/AppDefaults.swift b/Multiplatform/Shared/AppDefaults.swift new file mode 100644 index 000000000..5dc9f1910 --- /dev/null +++ b/Multiplatform/Shared/AppDefaults.swift @@ -0,0 +1,307 @@ +// +// AppDefaults.swift +// NetNewsWire +// +// Created by Maurice Parker on 6/28/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation + +enum UserInterfaceColorPalette: Int, CustomStringConvertible, CaseIterable { + case automatic = 0 + case light = 1 + case dark = 2 + + var description: String { + switch self { + case .automatic: + return NSLocalizedString("Automatic", comment: "Automatic") + case .light: + return NSLocalizedString("Light", comment: "Light") + case .dark: + return NSLocalizedString("Dark", comment: "Dark") + } + } + +} + +struct AppDefaults { + + #if os(macOS) + static var shared: UserDefaults = UserDefaults.standard + #endif + + #if os(iOS) + static var shared: UserDefaults = { + let appIdentifierPrefix = Bundle.main.object(forInfoDictionaryKey: "AppIdentifierPrefix") as! String + let suiteName = "\(appIdentifierPrefix)group.\(Bundle.main.bundleIdentifier!)" + return UserDefaults.init(suiteName: suiteName)! + }() + #endif + + struct Key { + static let refreshInterval = "refreshInterval" + static let hideDockUnreadCount = "JustinMillerHideDockUnreadCount" + static let userInterfaceColorPalette = "userInterfaceColorPalette" + static let activeExtensionPointIDs = "activeExtensionPointIDs" + static let lastImageCacheFlushDate = "lastImageCacheFlushDate" + static let firstRunDate = "firstRunDate" + static let timelineGroupByFeed = "timelineGroupByFeed" + static let refreshClearsReadArticles = "refreshClearsReadArticles" + static let timelineNumberOfLines = "timelineNumberOfLines" + static let timelineIconSize = "timelineIconSize" + static let timelineSortDirection = "timelineSortDirection" + static let articleFullscreenAvailable = "articleFullscreenAvailable" + static let articleFullscreenEnabled = "articleFullscreenEnabled" + static let confirmMarkAllAsRead = "confirmMarkAllAsRead" + static let lastRefresh = "lastRefresh" + static let addWebFeedAccountID = "addWebFeedAccountID" + static let addWebFeedFolderName = "addWebFeedFolderName" + static let addFolderAccountID = "addFolderAccountID" + } + + static let isDeveloperBuild: Bool = { + if let dev = Bundle.main.object(forInfoDictionaryKey: "DeveloperEntitlements") as? String, dev == "-dev" { + return true + } + return false + }() + + static let isFirstRun: Bool = { + if let _ = AppDefaults.shared.object(forKey: Key.firstRunDate) as? Date { + return false + } + firstRunDate = Date() + return true + }() + + static var refreshInterval: RefreshInterval { + get { + let rawValue = UserDefaults.standard.integer(forKey: Key.refreshInterval) + return RefreshInterval(rawValue: rawValue) ?? RefreshInterval.everyHour + } + set { + UserDefaults.standard.set(newValue.rawValue, forKey: Key.refreshInterval) + } + } + + static var hideDockUnreadCount: Bool { + return bool(for: Key.hideDockUnreadCount) + } + + static var userInterfaceColorPalette: UserInterfaceColorPalette { + get { + if let result = UserInterfaceColorPalette(rawValue: int(for: Key.userInterfaceColorPalette)) { + return result + } + return .automatic + } + set { + setInt(for: Key.userInterfaceColorPalette, newValue.rawValue) + } + } + + static var addWebFeedAccountID: String? { + get { + return string(for: Key.addWebFeedAccountID) + } + set { + setString(for: Key.addWebFeedAccountID, newValue) + } + } + + static var addWebFeedFolderName: String? { + get { + return string(for: Key.addWebFeedFolderName) + } + set { + setString(for: Key.addWebFeedFolderName, newValue) + } + } + + static var addFolderAccountID: String? { + get { + return string(for: Key.addFolderAccountID) + } + set { + setString(for: Key.addFolderAccountID, newValue) + } + } + + static var activeExtensionPointIDs: [[AnyHashable : AnyHashable]]? { + get { + return UserDefaults.standard.object(forKey: Key.activeExtensionPointIDs) as? [[AnyHashable : AnyHashable]] + } + set { + UserDefaults.standard.set(newValue, forKey: Key.activeExtensionPointIDs) + } + } + + static var lastImageCacheFlushDate: Date? { + get { + return date(for: Key.lastImageCacheFlushDate) + } + set { + setDate(for: Key.lastImageCacheFlushDate, newValue) + } + } + + static var timelineGroupByFeed: Bool { + get { + return bool(for: Key.timelineGroupByFeed) + } + set { + setBool(for: Key.timelineGroupByFeed, newValue) + } + } + + static var refreshClearsReadArticles: Bool { + get { + return bool(for: Key.refreshClearsReadArticles) + } + set { + setBool(for: Key.refreshClearsReadArticles, newValue) + } + } + + static var timelineSortDirection: ComparisonResult { + get { + return sortDirection(for: Key.timelineSortDirection) + } + set { + setSortDirection(for: Key.timelineSortDirection, newValue) + } + } + + static var articleFullscreenAvailable: Bool { + get { + return bool(for: Key.articleFullscreenAvailable) + } + set { + setBool(for: Key.articleFullscreenAvailable, newValue) + } + } + + static var articleFullscreenEnabled: Bool { + get { + return bool(for: Key.articleFullscreenEnabled) + } + set { + setBool(for: Key.articleFullscreenEnabled, newValue) + } + } + + static var confirmMarkAllAsRead: Bool { + get { + return bool(for: Key.confirmMarkAllAsRead) + } + set { + setBool(for: Key.confirmMarkAllAsRead, newValue) + } + } + + static var lastRefresh: Date? { + get { + return date(for: Key.lastRefresh) + } + set { + setDate(for: Key.lastRefresh, newValue) + } + } + + static var timelineNumberOfLines: Int { + get { + return int(for: Key.timelineNumberOfLines) + } + set { + setInt(for: Key.timelineNumberOfLines, newValue) + } + } + + static var timelineIconSize: IconSize { + get { + let rawValue = AppDefaults.shared.integer(forKey: Key.timelineIconSize) + return IconSize(rawValue: rawValue) ?? IconSize.medium + } + set { + AppDefaults.shared.set(newValue.rawValue, forKey: Key.timelineIconSize) + } + } + + static func registerDefaults() { + let defaults: [String : Any] = [Key.userInterfaceColorPalette: UserInterfaceColorPalette.automatic.rawValue, + Key.timelineGroupByFeed: false, + Key.refreshClearsReadArticles: false, + Key.timelineNumberOfLines: 2, + Key.timelineIconSize: IconSize.medium.rawValue, + Key.timelineSortDirection: ComparisonResult.orderedDescending.rawValue, + Key.articleFullscreenAvailable: false, + Key.articleFullscreenEnabled: false, + Key.confirmMarkAllAsRead: true] + AppDefaults.shared.register(defaults: defaults) + } + +} + +private extension AppDefaults { + + static var firstRunDate: Date? { + get { + return date(for: Key.firstRunDate) + } + set { + setDate(for: Key.firstRunDate, newValue) + } + } + + static func string(for key: String) -> String? { + return AppDefaults.shared.string(forKey: key) + } + + static func setString(for key: String, _ value: String?) { + AppDefaults.shared.set(value, forKey: key) + } + + static func bool(for key: String) -> Bool { + return AppDefaults.shared.bool(forKey: key) + } + + static func setBool(for key: String, _ flag: Bool) { + AppDefaults.shared.set(flag, forKey: key) + } + + static func int(for key: String) -> Int { + return AppDefaults.shared.integer(forKey: key) + } + + static func setInt(for key: String, _ x: Int) { + AppDefaults.shared.set(x, forKey: key) + } + + static func date(for key: String) -> Date? { + return AppDefaults.shared.object(forKey: key) as? Date + } + + static func setDate(for key: String, _ date: Date?) { + AppDefaults.shared.set(date, forKey: key) + } + + static func sortDirection(for key:String) -> ComparisonResult { + let rawInt = int(for: key) + if rawInt == ComparisonResult.orderedAscending.rawValue { + return .orderedAscending + } + return .orderedDescending + } + + static func setSortDirection(for key: String, _ value: ComparisonResult) { + if value == .orderedAscending { + setInt(for: key, ComparisonResult.orderedAscending.rawValue) + } + else { + setInt(for: key, ComparisonResult.orderedDescending.rawValue) + } + } + +} diff --git a/Multiplatform/Shared/Assets.xcassets/AccentColor.colorset/Contents.json b/Multiplatform/Shared/Assets.xcassets/AccentColor.colorset/Contents.json index eb8789700..bf3d625ad 100644 --- a/Multiplatform/Shared/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/Multiplatform/Shared/Assets.xcassets/AccentColor.colorset/Contents.json @@ -1,6 +1,33 @@ { "colors" : [ { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.933", + "green" : "0.416", + "red" : "0.031" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.945", + "green" : "0.502", + "red" : "0.176" + } + }, "idiom" : "universal" } ], diff --git a/Multiplatform/Shared/Assets.xcassets/accountCloudKit.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountCloudKit.imageset/Contents.json new file mode 100644 index 000000000..fc07d2975 --- /dev/null +++ b/Multiplatform/Shared/Assets.xcassets/accountCloudKit.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "icloud.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Multiplatform/Shared/Assets.xcassets/accountCloudKit.imageset/icloud.pdf b/Multiplatform/Shared/Assets.xcassets/accountCloudKit.imageset/icloud.pdf new file mode 100644 index 000000000..74406f4cb Binary files /dev/null and b/Multiplatform/Shared/Assets.xcassets/accountCloudKit.imageset/icloud.pdf differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/Contents.json new file mode 100644 index 000000000..5e337ee42 --- /dev/null +++ b/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "outline-512.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/outline-512.png b/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/outline-512.png new file mode 100644 index 000000000..f7d29faa1 Binary files /dev/null and b/Multiplatform/Shared/Assets.xcassets/accountFeedWrangler.imageset/outline-512.png differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountFeedbin.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountFeedbin.imageset/Contents.json new file mode 100644 index 000000000..a6213a3b1 --- /dev/null +++ b/Multiplatform/Shared/Assets.xcassets/accountFeedbin.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "accountFeedbin.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/Multiplatform/Shared/Assets.xcassets/accountFeedbin.imageset/accountFeedbin.pdf b/Multiplatform/Shared/Assets.xcassets/accountFeedbin.imageset/accountFeedbin.pdf new file mode 100644 index 000000000..d688a02e1 Binary files /dev/null and b/Multiplatform/Shared/Assets.xcassets/accountFeedbin.imageset/accountFeedbin.pdf differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountFeedly.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountFeedly.imageset/Contents.json new file mode 100644 index 000000000..4034be795 --- /dev/null +++ b/Multiplatform/Shared/Assets.xcassets/accountFeedly.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "accountFeedly.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Multiplatform/Shared/Assets.xcassets/accountFeedly.imageset/accountFeedly.pdf b/Multiplatform/Shared/Assets.xcassets/accountFeedly.imageset/accountFeedly.pdf new file mode 100644 index 000000000..907e486bc Binary files /dev/null and b/Multiplatform/Shared/Assets.xcassets/accountFeedly.imageset/accountFeedly.pdf differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountFreshRSS.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountFreshRSS.imageset/Contents.json new file mode 100644 index 000000000..64a29ab45 --- /dev/null +++ b/Multiplatform/Shared/Assets.xcassets/accountFreshRSS.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "accountFreshRSS.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Multiplatform/Shared/Assets.xcassets/accountFreshRSS.imageset/accountFreshRSS.pdf b/Multiplatform/Shared/Assets.xcassets/accountFreshRSS.imageset/accountFreshRSS.pdf new file mode 100644 index 000000000..1ff98e115 Binary files /dev/null and b/Multiplatform/Shared/Assets.xcassets/accountFreshRSS.imageset/accountFreshRSS.pdf differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountLocalMac.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountLocalMac.imageset/Contents.json new file mode 100644 index 000000000..6d51578a9 --- /dev/null +++ b/Multiplatform/Shared/Assets.xcassets/accountLocalMac.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "localAccountMac.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Multiplatform/Shared/Assets.xcassets/accountLocalMac.imageset/localAccountMac.pdf b/Multiplatform/Shared/Assets.xcassets/accountLocalMac.imageset/localAccountMac.pdf new file mode 100644 index 000000000..5c3292ce0 Binary files /dev/null and b/Multiplatform/Shared/Assets.xcassets/accountLocalMac.imageset/localAccountMac.pdf differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountLocalPad.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountLocalPad.imageset/Contents.json new file mode 100644 index 000000000..0ac81354e --- /dev/null +++ b/Multiplatform/Shared/Assets.xcassets/accountLocalPad.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "localAccountPad.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/Multiplatform/Shared/Assets.xcassets/accountLocalPad.imageset/localAccountPad.pdf b/Multiplatform/Shared/Assets.xcassets/accountLocalPad.imageset/localAccountPad.pdf new file mode 100644 index 000000000..cfc6ca9f0 Binary files /dev/null and b/Multiplatform/Shared/Assets.xcassets/accountLocalPad.imageset/localAccountPad.pdf differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountLocalPhone.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountLocalPhone.imageset/Contents.json new file mode 100644 index 000000000..1e6c82f37 --- /dev/null +++ b/Multiplatform/Shared/Assets.xcassets/accountLocalPhone.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "localAccountPhone.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/Multiplatform/Shared/Assets.xcassets/accountLocalPhone.imageset/localAccountPhone.pdf b/Multiplatform/Shared/Assets.xcassets/accountLocalPhone.imageset/localAccountPhone.pdf new file mode 100644 index 000000000..c807edece Binary files /dev/null and b/Multiplatform/Shared/Assets.xcassets/accountLocalPhone.imageset/localAccountPhone.pdf differ diff --git a/Multiplatform/Shared/Assets.xcassets/accountNewsBlur.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/accountNewsBlur.imageset/Contents.json new file mode 100644 index 000000000..9b23ec7d3 --- /dev/null +++ b/Multiplatform/Shared/Assets.xcassets/accountNewsBlur.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "newsblur-512.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Multiplatform/Shared/Assets.xcassets/accountNewsBlur.imageset/newsblur-512.png b/Multiplatform/Shared/Assets.xcassets/accountNewsBlur.imageset/newsblur-512.png new file mode 100644 index 000000000..5fab67691 Binary files /dev/null and b/Multiplatform/Shared/Assets.xcassets/accountNewsBlur.imageset/newsblur-512.png differ diff --git a/Multiplatform/Shared/Assets.xcassets/extensionPointMarsEdit.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/extensionPointMarsEdit.imageset/Contents.json new file mode 100644 index 000000000..432281179 --- /dev/null +++ b/Multiplatform/Shared/Assets.xcassets/extensionPointMarsEdit.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "MarsEditOfficial.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Multiplatform/Shared/Assets.xcassets/extensionPointMarsEdit.imageset/MarsEditOfficial.pdf b/Multiplatform/Shared/Assets.xcassets/extensionPointMarsEdit.imageset/MarsEditOfficial.pdf new file mode 100644 index 000000000..82396a369 Binary files /dev/null and b/Multiplatform/Shared/Assets.xcassets/extensionPointMarsEdit.imageset/MarsEditOfficial.pdf differ diff --git a/Multiplatform/Shared/Assets.xcassets/extensionPointMicroblog.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/extensionPointMicroblog.imageset/Contents.json new file mode 100644 index 000000000..4db95b5b7 --- /dev/null +++ b/Multiplatform/Shared/Assets.xcassets/extensionPointMicroblog.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "micro-dot-blog.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Multiplatform/Shared/Assets.xcassets/extensionPointMicroblog.imageset/micro-dot-blog.pdf b/Multiplatform/Shared/Assets.xcassets/extensionPointMicroblog.imageset/micro-dot-blog.pdf new file mode 100644 index 000000000..ad66dd377 Binary files /dev/null and b/Multiplatform/Shared/Assets.xcassets/extensionPointMicroblog.imageset/micro-dot-blog.pdf differ diff --git a/Multiplatform/Shared/Assets.xcassets/extensionPointReddit.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/extensionPointReddit.imageset/Contents.json new file mode 100644 index 000000000..1a49be3b3 --- /dev/null +++ b/Multiplatform/Shared/Assets.xcassets/extensionPointReddit.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "filename" : "reddit-light.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "reddit-dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Multiplatform/Shared/Assets.xcassets/extensionPointReddit.imageset/reddit-dark.pdf b/Multiplatform/Shared/Assets.xcassets/extensionPointReddit.imageset/reddit-dark.pdf new file mode 100644 index 000000000..b4b6d7419 Binary files /dev/null and b/Multiplatform/Shared/Assets.xcassets/extensionPointReddit.imageset/reddit-dark.pdf differ diff --git a/Multiplatform/Shared/Assets.xcassets/extensionPointReddit.imageset/reddit-light.pdf b/Multiplatform/Shared/Assets.xcassets/extensionPointReddit.imageset/reddit-light.pdf new file mode 100644 index 000000000..4d6267b63 Binary files /dev/null and b/Multiplatform/Shared/Assets.xcassets/extensionPointReddit.imageset/reddit-light.pdf differ diff --git a/Multiplatform/Shared/Assets.xcassets/extensionPointTwitter.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/extensionPointTwitter.imageset/Contents.json new file mode 100644 index 000000000..834100cda --- /dev/null +++ b/Multiplatform/Shared/Assets.xcassets/extensionPointTwitter.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "twitter.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Multiplatform/Shared/Assets.xcassets/extensionPointTwitter.imageset/twitter.pdf b/Multiplatform/Shared/Assets.xcassets/extensionPointTwitter.imageset/twitter.pdf new file mode 100644 index 000000000..e50de4443 Binary files /dev/null and b/Multiplatform/Shared/Assets.xcassets/extensionPointTwitter.imageset/twitter.pdf differ diff --git a/Multiplatform/Shared/Assets.xcassets/faviconTemplateImage.imageset/Contents.json b/Multiplatform/Shared/Assets.xcassets/faviconTemplateImage.imageset/Contents.json new file mode 100644 index 000000000..bd3a2fbbe --- /dev/null +++ b/Multiplatform/Shared/Assets.xcassets/faviconTemplateImage.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "faviconTemplateImage.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Multiplatform/Shared/Assets.xcassets/faviconTemplateImage.imageset/faviconTemplateImage.pdf b/Multiplatform/Shared/Assets.xcassets/faviconTemplateImage.imageset/faviconTemplateImage.pdf new file mode 100644 index 000000000..d6bcb5b69 Binary files /dev/null and b/Multiplatform/Shared/Assets.xcassets/faviconTemplateImage.imageset/faviconTemplateImage.pdf differ diff --git a/Multiplatform/Shared/ErrorHandler.swift b/Multiplatform/Shared/ErrorHandler.swift new file mode 100644 index 000000000..786dff5d2 --- /dev/null +++ b/Multiplatform/Shared/ErrorHandler.swift @@ -0,0 +1,31 @@ +// +// ErrorHandler.swift +// NetNewsWire +// +// Created by Maurice Parker on 6/28/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation +import RSCore +import os.log + +struct ErrorHandler { + + private static var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application") + +// public static func present(_ viewController: UIViewController) -> (Error) -> () { +// return { [weak viewController] error in +// if UIApplication.shared.applicationState == .active { +// viewController?.presentError(error) +// } else { +// ErrorHandler.log(error) +// } +// } +// } + + public static func log(_ error: Error) { + os_log(.error, log: self.log, "%@", error.localizedDescription) + } + +} diff --git a/Multiplatform/Shared/NetNewsWire_MultiplatformApp.swift b/Multiplatform/Shared/NetNewsWire.swift similarity index 50% rename from Multiplatform/Shared/NetNewsWire_MultiplatformApp.swift rename to Multiplatform/Shared/NetNewsWire.swift index 4173d0887..720ac2677 100644 --- a/Multiplatform/Shared/NetNewsWire_MultiplatformApp.swift +++ b/Multiplatform/Shared/NetNewsWire.swift @@ -1,5 +1,5 @@ // -// NetNewsWire_MultiplatformApp.swift +// NetNewsWire.swift // Shared // // Created by Maurice Parker on 6/27/20. @@ -9,7 +9,15 @@ import SwiftUI @main -struct NetNewsWire_MultiplatformApp: App { +struct NetNewsWire: App { + + #if os(macOS) + @NSApplicationDelegateAdaptor(AppDelegate.self) private var delegate + #endif + #if os(iOS) + @UIApplicationDelegateAdaptor(AppDelegate.self) private var delegate + #endif + var body: some Scene { WindowGroup { ContentView() diff --git a/Multiplatform/iOS/AppDelegate.swift b/Multiplatform/iOS/AppDelegate.swift new file mode 100644 index 000000000..6124a75bb --- /dev/null +++ b/Multiplatform/iOS/AppDelegate.swift @@ -0,0 +1,390 @@ +// +// AppDelegate.swift +// Multiplatform iOS +// +// Created by Maurice Parker on 6/28/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import UIKit +import RSCore +import RSWeb +import Account +import BackgroundTasks +import os.log + +var appDelegate: AppDelegate! + +class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, UnreadCountProvider { + + private var bgTaskDispatchQueue = DispatchQueue.init(label: "BGTaskScheduler") + + private var waitBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid + private var syncBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid + + var syncTimer: ArticleStatusSyncTimer? + + var shuttingDown = false { + didSet { + if shuttingDown { + syncTimer?.shuttingDown = shuttingDown + syncTimer?.invalidate() + } + } + } + + var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application") + + var userNotificationManager: UserNotificationManager! + var faviconDownloader: FaviconDownloader! + var imageDownloader: ImageDownloader! + var authorAvatarDownloader: AuthorAvatarDownloader! + var webFeedIconDownloader: WebFeedIconDownloader! + // TODO: Add Extension back in +// var extensionContainersFile: ExtensionContainersFile! +// var extensionFeedAddRequestFile: ExtensionFeedAddRequestFile! + + var unreadCount = 0 { + didSet { + if unreadCount != oldValue { + postUnreadCountDidChangeNotification() + UIApplication.shared.applicationIconBadgeNumber = unreadCount + } + } + } + + var isSyncArticleStatusRunning = false + var isWaitingForSyncTasks = false + + override init() { + super.init() + appDelegate = self + + let documentAccountURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! + let documentAccountsFolder = documentAccountURL.appendingPathComponent("Accounts").absoluteString + let documentAccountsFolderPath = String(documentAccountsFolder.suffix(from: documentAccountsFolder.index(documentAccountsFolder.startIndex, offsetBy: 7))) + AccountManager.shared = AccountManager(accountsFolder: documentAccountsFolderPath) + FeedProviderManager.shared.delegate = ExtensionPointManager.shared + + NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(accountRefreshDidFinish(_:)), name: .AccountRefreshDidFinish, object: nil) + } + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + AppDefaults.registerDefaults() + + let isFirstRun = AppDefaults.isFirstRun + if isFirstRun { + os_log("Is first run.", log: log, type: .info) + } + + if isFirstRun && !AccountManager.shared.anyAccountHasAtLeastOneFeed() { + let localAccount = AccountManager.shared.defaultAccount + DefaultFeedsImporter.importDefaultFeeds(account: localAccount) + } + + registerBackgroundTasks() + CacheCleaner.purgeIfNecessary() + initializeDownloaders() + initializeHomeScreenQuickActions() + + DispatchQueue.main.async { + self.unreadCount = AccountManager.shared.unreadCount + } + + UNUserNotificationCenter.current().getNotificationSettings { (settings) in + if settings.authorizationStatus == .authorized { + DispatchQueue.main.async { + UIApplication.shared.registerForRemoteNotifications() + } + } + } + + UNUserNotificationCenter.current().delegate = self + userNotificationManager = UserNotificationManager() + +// extensionContainersFile = ExtensionContainersFile() +// extensionFeedAddRequestFile = ExtensionFeedAddRequestFile() + + syncTimer = ArticleStatusSyncTimer() + + #if DEBUG + syncTimer!.update() + #endif + + return true + + } + + func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + DispatchQueue.main.async { + self.resumeDatabaseProcessingIfNecessary() + AccountManager.shared.receiveRemoteNotification(userInfo: userInfo) { + self.suspendApplication() + completionHandler(.newData) + } + } + } + + func applicationWillTerminate(_ application: UIApplication) { + shuttingDown = true + } + + // MARK: Notifications + + @objc func unreadCountDidChange(_ note: Notification) { + if note.object is AccountManager { + unreadCount = AccountManager.shared.unreadCount + } + } + + @objc func accountRefreshDidFinish(_ note: Notification) { + AppDefaults.lastRefresh = Date() + } + + // MARK: - API + + func resumeDatabaseProcessingIfNecessary() { + if AccountManager.shared.isSuspended { + AccountManager.shared.resumeAll() + os_log("Application processing resumed.", log: self.log, type: .info) + } + } + + func prepareAccountsForBackground() { +// extensionFeedAddRequestFile.suspend() + syncTimer?.invalidate() + scheduleBackgroundFeedRefresh() + syncArticleStatus() + waitForSyncTasksToFinish() + } + + func prepareAccountsForForeground() { +// extensionFeedAddRequestFile.resume() + syncTimer?.update() + + if let lastRefresh = AppDefaults.lastRefresh { + if Date() > lastRefresh.addingTimeInterval(15 * 60) { + AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) + } else { + AccountManager.shared.syncArticleStatusAll() + } + } else { + AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) + } + } + + func logMessage(_ message: String, type: LogItem.ItemType) { + print("logMessage: \(message) - \(type)") + + } + + func logDebugMessage(_ message: String) { + logMessage(message, type: .debug) + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + completionHandler([.banner, .badge, .sound]) + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + defer { completionHandler() } + + // TODO: Add back in User Notification handling +// if let sceneDelegate = response.targetScene?.delegate as? SceneDelegate { +// sceneDelegate.handle(response) +// } + + } + +} + +// MARK: App Initialization + +private extension AppDelegate { + + private func initializeDownloaders() { + let tempDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first! + let faviconsFolderURL = tempDir.appendingPathComponent("Favicons") + let imagesFolderURL = tempDir.appendingPathComponent("Images") + + try! FileManager.default.createDirectory(at: faviconsFolderURL, withIntermediateDirectories: true, attributes: nil) + let faviconsFolder = faviconsFolderURL.absoluteString + let faviconsFolderPath = faviconsFolder.suffix(from: faviconsFolder.index(faviconsFolder.startIndex, offsetBy: 7)) + faviconDownloader = FaviconDownloader(folder: String(faviconsFolderPath)) + + let imagesFolder = imagesFolderURL.absoluteString + let imagesFolderPath = imagesFolder.suffix(from: imagesFolder.index(imagesFolder.startIndex, offsetBy: 7)) + try! FileManager.default.createDirectory(at: imagesFolderURL, withIntermediateDirectories: true, attributes: nil) + imageDownloader = ImageDownloader(folder: String(imagesFolderPath)) + + authorAvatarDownloader = AuthorAvatarDownloader(imageDownloader: imageDownloader) + + let tempFolder = tempDir.absoluteString + let tempFolderPath = tempFolder.suffix(from: tempFolder.index(tempFolder.startIndex, offsetBy: 7)) + webFeedIconDownloader = WebFeedIconDownloader(imageDownloader: imageDownloader, folder: String(tempFolderPath)) + } + + private func initializeHomeScreenQuickActions() { + let unreadTitle = NSLocalizedString("First Unread", comment: "First Unread") + let unreadIcon = UIApplicationShortcutIcon(systemImageName: "chevron.down.circle") + let unreadItem = UIApplicationShortcutItem(type: "com.ranchero.NetNewsWire.FirstUnread", localizedTitle: unreadTitle, localizedSubtitle: nil, icon: unreadIcon, userInfo: nil) + + let searchTitle = NSLocalizedString("Search", comment: "Search") + let searchIcon = UIApplicationShortcutIcon(systemImageName: "magnifyingglass") + let searchItem = UIApplicationShortcutItem(type: "com.ranchero.NetNewsWire.ShowSearch", localizedTitle: searchTitle, localizedSubtitle: nil, icon: searchIcon, userInfo: nil) + + let addTitle = NSLocalizedString("Add Feed", comment: "Add Feed") + let addIcon = UIApplicationShortcutIcon(systemImageName: "plus") + let addItem = UIApplicationShortcutItem(type: "com.ranchero.NetNewsWire.ShowAdd", localizedTitle: addTitle, localizedSubtitle: nil, icon: addIcon, userInfo: nil) + + UIApplication.shared.shortcutItems = [addItem, searchItem, unreadItem] + } + +} + +// MARK: Go To Background + +private extension AppDelegate { + + func waitForSyncTasksToFinish() { + guard !isWaitingForSyncTasks && UIApplication.shared.applicationState == .background else { return } + + isWaitingForSyncTasks = true + + self.waitBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask { [weak self] in + guard let self = self else { return } + self.completeProcessing(true) + os_log("Accounts wait for progress terminated for running too long.", log: self.log, type: .info) + } + + DispatchQueue.main.async { [weak self] in + self?.waitToComplete() { [weak self] suspend in + self?.completeProcessing(suspend) + } + } + } + + func waitToComplete(completion: @escaping (Bool) -> Void) { + guard UIApplication.shared.applicationState == .background else { + os_log("App came back to forground, no longer waiting.", log: self.log, type: .info) + completion(false) + return + } + + if AccountManager.shared.refreshInProgress || isSyncArticleStatusRunning { + os_log("Waiting for sync to finish...", log: self.log, type: .info) + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in + self?.waitToComplete(completion: completion) + } + } else { + os_log("Refresh progress complete.", log: self.log, type: .info) + completion(true) + } + } + + func completeProcessing(_ suspend: Bool) { + if suspend { + suspendApplication() + } + UIApplication.shared.endBackgroundTask(self.waitBackgroundUpdateTask) + self.waitBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid + isWaitingForSyncTasks = false + } + + func syncArticleStatus() { + guard !isSyncArticleStatusRunning else { return } + + isSyncArticleStatusRunning = true + + let completeProcessing = { [unowned self] in + self.isSyncArticleStatusRunning = false + UIApplication.shared.endBackgroundTask(self.syncBackgroundUpdateTask) + self.syncBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid + } + + self.syncBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask { + completeProcessing() + os_log("Accounts sync processing terminated for running too long.", log: self.log, type: .info) + } + + DispatchQueue.main.async { + AccountManager.shared.syncArticleStatusAll() { + completeProcessing() + } + } + } + + func suspendApplication() { + guard UIApplication.shared.applicationState == .background else { return } + + AccountManager.shared.suspendNetworkAll() + AccountManager.shared.suspendDatabaseAll() + CoalescingQueue.standard.performCallsImmediately() + + os_log("Application processing suspended.", log: self.log, type: .info) + } + +} + +// MARK: Background Tasks + +private extension AppDelegate { + + /// Register all background tasks. + func registerBackgroundTasks() { + // Register background feed refresh. + BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.ranchero.NetNewsWire.FeedRefresh", using: nil) { (task) in + self.performBackgroundFeedRefresh(with: task as! BGAppRefreshTask) + } + } + + /// Schedules a background app refresh based on `AppDefaults.refreshInterval`. + func scheduleBackgroundFeedRefresh() { + let request = BGAppRefreshTaskRequest(identifier: "com.ranchero.NetNewsWire.FeedRefresh") + request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) + + // We send this to a dedicated serial queue because as of 11/05/19 on iOS 13.2 the call to the + // task scheduler can hang indefinitely. + bgTaskDispatchQueue.async { + do { + try BGTaskScheduler.shared.submit(request) + } catch { + os_log(.error, log: self.log, "Could not schedule app refresh: %@", error.localizedDescription) + } + } + } + + /// Performs background feed refresh. + /// - Parameter task: `BGAppRefreshTask` + /// - Warning: As of Xcode 11 beta 2, when triggered from the debugger this doesn't work. + func performBackgroundFeedRefresh(with task: BGAppRefreshTask) { + + scheduleBackgroundFeedRefresh() // schedule next refresh + + os_log("Woken to perform account refresh.", log: self.log, type: .info) + + DispatchQueue.main.async { + if AccountManager.shared.isSuspended { + AccountManager.shared.resumeAll() + } + AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) { [unowned self] in + if !AccountManager.shared.isSuspended { + self.suspendApplication() + os_log("Account refresh operation completed.", log: self.log, type: .info) + task.setTaskCompleted(success: true) + } + } + } + + // set expiration handler + task.expirationHandler = { [weak task] in + DispatchQueue.main.sync { + self.suspendApplication() + } + os_log("Accounts refresh processing terminated for running too long.", log: self.log, type: .info) + task?.setTaskCompleted(success: false) + } + } + +} diff --git a/Multiplatform/iOS/Info.plist b/Multiplatform/iOS/Info.plist index efc211a0c..0db629280 100644 --- a/Multiplatform/iOS/Info.plist +++ b/Multiplatform/iOS/Info.plist @@ -46,5 +46,40 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + BGTaskSchedulerPermittedIdentifiers + + com.ranchero.NetNewsWire.FeedRefresh + + NSPhotoLibraryAddUsageDescription + Grant permission to save images from the article. + NSUserActivityTypes + + AddWebFeedIntent + NextUnread + ReadArticle + Restoration + SelectFeed + + UIBackgroundModes + + fetch + processing + remote-notification + + UserAgent + NetNewsWire (RSS Reader; https://ranchero.com/netnewswire/) + OrganizationIdentifier + $(ORGANIZATION_IDENTIFIER) + DeveloperEntitlements + $(DEVELOPER_ENTITLEMENTS) + AppGroup + group.$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS + AppIdentifierPrefix + $(AppIdentifierPrefix) diff --git a/Multiplatform/macOS/AppDelegate.swift b/Multiplatform/macOS/AppDelegate.swift new file mode 100644 index 000000000..f642ce2e8 --- /dev/null +++ b/Multiplatform/macOS/AppDelegate.swift @@ -0,0 +1,286 @@ +// +// AppDelegate.swift +// Multiplatform macOS +// +// Created by Maurice Parker on 6/28/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import AppKit +import os.log +import UserNotifications +import Articles +import RSTree +import RSWeb +import Account +import RSCore + +// If we're not going to import Sparkle, provide dummy protocols to make it easy +// for AppDelegate to comply +#if MAC_APP_STORE || TEST +protocol SPUStandardUserDriverDelegate {} +protocol SPUUpdaterDelegate {} +#else +import Sparkle +#endif + +var appDelegate: AppDelegate! + +class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate, UnreadCountProvider, SPUStandardUserDriverDelegate, SPUUpdaterDelegate +{ + + private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application") + + var userNotificationManager: UserNotificationManager! + var faviconDownloader: FaviconDownloader! + var imageDownloader: ImageDownloader! + var authorAvatarDownloader: AuthorAvatarDownloader! + var webFeedIconDownloader: WebFeedIconDownloader! + + var refreshTimer: AccountRefreshTimer? + var syncTimer: ArticleStatusSyncTimer? + + var shuttingDown = false { + didSet { + if shuttingDown { + refreshTimer?.shuttingDown = shuttingDown + refreshTimer?.invalidate() + syncTimer?.shuttingDown = shuttingDown + syncTimer?.invalidate() + } + } + } + + var unreadCount = 0 { + didSet { + if unreadCount != oldValue { + CoalescingQueue.standard.add(self, #selector(updateDockBadge)) + postUnreadCountDidChangeNotification() + } + } + } + + var appName: String! + + private let appNewsURLString = "https://nnw.ranchero.com/feed.json" + private let appMovementMonitor = RSAppMovementMonitor() + #if !MAC_APP_STORE && !TEST + private var softwareUpdater: SPUUpdater! + #endif + + override init() { + NSWindow.allowsAutomaticWindowTabbing = false + super.init() + + AccountManager.shared = AccountManager(accountsFolder: Platform.dataSubfolder(forApplication: nil, folderName: "Accounts")!) + FeedProviderManager.shared.delegate = ExtensionPointManager.shared + + NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) + NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(didWakeNotification(_:)), name: NSWorkspace.didWakeNotification, object: nil) + + appDelegate = self + } + + // MARK: - NSApplicationDelegate + + func applicationWillFinishLaunching(_ notification: Notification) { + // TODO: add Apple Events back in +// installAppleEventHandlers() + + CacheCleaner.purgeIfNecessary() + + // Try to establish a cache in the Caches folder, but if it fails for some reason fall back to a temporary dir + let cacheFolder: String + if let userCacheFolder = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false).path { + cacheFolder = userCacheFolder + } + else { + let bundleIdentifier = (Bundle.main.infoDictionary!["CFBundleIdentifier"]! as! String) + cacheFolder = (NSTemporaryDirectory() as NSString).appendingPathComponent(bundleIdentifier) + } + + let faviconsFolder = (cacheFolder as NSString).appendingPathComponent("Favicons") + let faviconsFolderURL = URL(fileURLWithPath: faviconsFolder) + try! FileManager.default.createDirectory(at: faviconsFolderURL, withIntermediateDirectories: true, attributes: nil) + faviconDownloader = FaviconDownloader(folder: faviconsFolder) + + let imagesFolder = (cacheFolder as NSString).appendingPathComponent("Images") + let imagesFolderURL = URL(fileURLWithPath: imagesFolder) + try! FileManager.default.createDirectory(at: imagesFolderURL, withIntermediateDirectories: true, attributes: nil) + imageDownloader = ImageDownloader(folder: imagesFolder) + + authorAvatarDownloader = AuthorAvatarDownloader(imageDownloader: imageDownloader) + webFeedIconDownloader = WebFeedIconDownloader(imageDownloader: imageDownloader, folder: cacheFolder) + + appName = (Bundle.main.infoDictionary!["CFBundleExecutable"]! as! String) + } + + func applicationDidFinishLaunching(_ note: Notification) { + + #if MAC_APP_STORE || TEST + checkForUpdatesMenuItem.isHidden = true + #else + // Initialize Sparkle... + let hostBundle = Bundle.main + let updateDriver = SPUStandardUserDriver(hostBundle: hostBundle, delegate: self) + self.softwareUpdater = SPUUpdater(hostBundle: hostBundle, applicationBundle: hostBundle, userDriver: updateDriver, delegate: self) + + do { + try self.softwareUpdater.start() + } + catch { + NSLog("Failed to start software updater with error: \(error)") + } + #endif + + AppDefaults.registerDefaults() + let isFirstRun = AppDefaults.isFirstRun + if isFirstRun { + os_log(.debug, log: log, "Is first run.") + } + let localAccount = AccountManager.shared.defaultAccount + + if isFirstRun && !AccountManager.shared.anyAccountHasAtLeastOneFeed() { + DefaultFeedsImporter.importDefaultFeeds(account: localAccount) + } + + + NotificationCenter.default.addObserver(self, selector: #selector(webFeedSettingDidChange(_:)), name: .WebFeedSettingDidChange, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil) + + DispatchQueue.main.async { + self.unreadCount = AccountManager.shared.unreadCount + } + + refreshTimer = AccountRefreshTimer() + syncTimer = ArticleStatusSyncTimer() + + UNUserNotificationCenter.current().requestAuthorization(options:[.badge, .sound, .alert]) { (granted, error) in } + NSApplication.shared.registerForRemoteNotifications() + + UNUserNotificationCenter.current().delegate = self + userNotificationManager = UserNotificationManager() + + // TODO: Add a debug menu +// if AppDefaults.showDebugMenu { +// refreshTimer!.update() +// syncTimer!.update() +// +// // The Web Inspector uses SPI and can never appear in a MAC_APP_STORE build. +// #if MAC_APP_STORE +// let debugMenu = debugMenuItem.submenu! +// let toggleWebInspectorItemIndex = debugMenu.indexOfItem(withTarget: self, andAction: #selector(toggleWebInspectorEnabled(_:))) +// if toggleWebInspectorItemIndex != -1 { +// debugMenu.removeItem(at: toggleWebInspectorItemIndex) +// } +// #endif +// } else { +// debugMenuItem.menu?.removeItem(debugMenuItem) +// DispatchQueue.main.async { +// self.refreshTimer!.timedRefresh(nil) +// self.syncTimer!.timedRefresh(nil) +// } +// } + + // TODO: Add back in crash reporter +// #if !MAC_APP_STORE +// DispatchQueue.main.async { +// CrashReporter.check(appName: "NetNewsWire") +// } +// #endif + + } + + func applicationDidBecomeActive(_ notification: Notification) { + fireOldTimers() + } + + func applicationDidResignActive(_ notification: Notification) { + ArticleStringFormatter.emptyCaches() + } + + func application(_ application: NSApplication, didReceiveRemoteNotification userInfo: [String : Any]) { + AccountManager.shared.receiveRemoteNotification(userInfo: userInfo) + } + + func applicationWillTerminate(_ notification: Notification) { + shuttingDown = true + } + + // MARK: Notifications + @objc func unreadCountDidChange(_ note: Notification) { + if note.object is AccountManager { + unreadCount = AccountManager.shared.unreadCount + } + } + + @objc func webFeedSettingDidChange(_ note: Notification) { + guard let feed = note.object as? WebFeed, let key = note.userInfo?[WebFeed.WebFeedSettingUserInfoKey] as? String else { + return + } + if key == WebFeed.WebFeedSettingKey.homePageURL || key == WebFeed.WebFeedSettingKey.faviconURL { + let _ = faviconDownloader.favicon(for: feed) + } + } + + @objc func userDefaultsDidChange(_ note: Notification) { + refreshTimer?.update() + updateDockBadge() + } + + @objc func didWakeNotification(_ note: Notification) { + fireOldTimers() + } + + // MARK: UNUserNotificationCenterDelegate + + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + completionHandler([.banner, .badge, .sound]) + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { +// TODO: Add back in Notification handling +// mainWindowController?.handle(response) + completionHandler() + } + + // MARK: - Dock Badge + @objc func updateDockBadge() { + let label = unreadCount > 0 && !AppDefaults.hideDockUnreadCount ? "\(unreadCount)" : "" + NSApplication.shared.dockTile.badgeLabel = label + } + +} + +private extension AppDelegate { + + func fireOldTimers() { + // It’s possible there’s a refresh timer set to go off in the past. + // In that case, refresh now and update the timer. + refreshTimer?.fireOldTimer() + syncTimer?.fireOldTimer() + } + +} + +/* + the ScriptingAppDelegate protocol exposes a narrow set of accessors with + internal visibility which are very similar to some private vars. + + These would be unnecessary if the similar accessors were marked internal rather than private, + but for now, we'll keep the stratification of visibility +*/ +//extension AppDelegate : ScriptingAppDelegate { +// +// internal var scriptingMainWindowController: ScriptingMainWindowController? { +// return mainWindowController +// } +// +// internal var scriptingCurrentArticle: Article? { +// return self.scriptingMainWindowController?.scriptingCurrentArticle +// } +// +// internal var scriptingSelectedArticles: [Article] { +// return self.scriptingMainWindowController?.scriptingSelectedArticles ?? [] +// } +//} diff --git a/Multiplatform/macOS/Info.plist b/Multiplatform/macOS/Info.plist index 705980986..a4af5e913 100644 --- a/Multiplatform/macOS/Info.plist +++ b/Multiplatform/macOS/Info.plist @@ -24,5 +24,16 @@ $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright Copyright © 2020 Ranchero Software. All rights reserved. + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UserAgent + NetNewsWire (RSS Reader; https://ranchero.com/netnewswire/) + OrganizationIdentifier + $(ORGANIZATION_IDENTIFIER) + DeveloperEntitlements + $(DEVELOPER_ENTITLEMENTS) diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index bd14b6619..00b2b37e8 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -251,8 +251,8 @@ 51BC4B00247277E0000A6ED8 /* URL-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */; }; 51BC4B01247277E0000A6ED8 /* URL-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */; }; 51BEB22D2451E8340066DEDD /* TwitterEnterDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BEB22C2451E8340066DEDD /* TwitterEnterDetailTableViewController.swift */; }; - 51C0515E24A77DF800194D5E /* NetNewsWire_MultiplatformApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C0513624A77DF700194D5E /* NetNewsWire_MultiplatformApp.swift */; }; - 51C0515F24A77DF800194D5E /* NetNewsWire_MultiplatformApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C0513624A77DF700194D5E /* NetNewsWire_MultiplatformApp.swift */; }; + 51C0515E24A77DF800194D5E /* NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C0513624A77DF700194D5E /* NetNewsWire.swift */; }; + 51C0515F24A77DF800194D5E /* NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C0513624A77DF700194D5E /* NetNewsWire.swift */; }; 51C0516024A77DF800194D5E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C0513724A77DF700194D5E /* ContentView.swift */; }; 51C0516124A77DF800194D5E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C0513724A77DF700194D5E /* ContentView.swift */; }; 51C0516224A77DF800194D5E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 51C0513824A77DF800194D5E /* Assets.xcassets */; }; @@ -344,6 +344,189 @@ 51E3EB3D229AB08300645299 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB3C229AB08300645299 /* ErrorHandler.swift */; }; 51E43962238037C400015C31 /* AddWebFeedFolderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E43961238037C400015C31 /* AddWebFeedFolderViewController.swift */; }; 51E4398023805EBC00015C31 /* AddComboTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4397F23805EBC00015C31 /* AddComboTableViewCell.swift */; }; + 51E4987F24A8061400B667CB /* Account.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407166A2262A60D00344432 /* Account.framework */; }; + 51E4988024A8061400B667CB /* Account.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407166A2262A60D00344432 /* Account.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E4988124A8061400B667CB /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 840716732262A60F00344432 /* Articles.framework */; }; + 51E4988224A8061400B667CB /* Articles.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 840716732262A60F00344432 /* Articles.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E4988324A8061400B667CB /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; }; + 51E4988424A8061400B667CB /* ArticlesDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E4988524A8061400B667CB /* OAuthSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 517A755324451BD500B553B9 /* OAuthSwift.framework */; }; + 51E4988624A8061400B667CB /* OAuthSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 517A755324451BD500B553B9 /* OAuthSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E4988724A8061400B667CB /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8520DD8CF200CA8CF5 /* RSCore.framework */; }; + 51E4988824A8061400B667CB /* RSCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8520DD8CF200CA8CF5 /* RSCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E4988924A8061400B667CB /* RSDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FC420DD8E0C00CA8CF5 /* RSDatabase.framework */; }; + 51E4988A24A8061400B667CB /* RSDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FC420DD8E0C00CA8CF5 /* RSDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E4988B24A8061400B667CB /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8C20DD8CF800CA8CF5 /* RSParser.framework */; }; + 51E4988C24A8061400B667CB /* RSParser.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8C20DD8CF800CA8CF5 /* RSParser.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E4988D24A8061400B667CB /* RSTree.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */; }; + 51E4988E24A8061400B667CB /* RSTree.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E4988F24A8061400B667CB /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FA320DD8D0500CA8CF5 /* RSWeb.framework */; }; + 51E4989024A8061400B667CB /* RSWeb.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FA320DD8D0500CA8CF5 /* RSWeb.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E4989124A8061400B667CB /* Secrets.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5102FD7B244008A700534F17 /* Secrets.framework */; }; + 51E4989224A8061400B667CB /* Secrets.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5102FD7B244008A700534F17 /* Secrets.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E4989324A8061400B667CB /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; }; + 51E4989424A8061400B667CB /* SyncDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E4989724A8065700B667CB /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51E4989624A8065700B667CB /* CloudKit.framework */; }; + 51E4989924A8067000B667CB /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51E4989824A8067000B667CB /* WebKit.framework */; }; + 51E4989A24A8069300B667CB /* Account.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407166A2262A60D00344432 /* Account.framework */; }; + 51E4989B24A8069300B667CB /* Account.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407166A2262A60D00344432 /* Account.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E4989C24A8069300B667CB /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 840716732262A60F00344432 /* Articles.framework */; }; + 51E4989D24A8069300B667CB /* Articles.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 840716732262A60F00344432 /* Articles.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E4989E24A8069300B667CB /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; }; + 51E4989F24A8069300B667CB /* ArticlesDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E498A024A8069300B667CB /* OAuthSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 517A755524451BD500B553B9 /* OAuthSwift.framework */; }; + 51E498A124A8069300B667CB /* OAuthSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 517A755524451BD500B553B9 /* OAuthSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E498A224A8069300B667CB /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8120DD8CF200CA8CF5 /* RSCore.framework */; }; + 51E498A324A8069300B667CB /* RSCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8120DD8CF200CA8CF5 /* RSCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E498A424A8069300B667CB /* RSDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FC020DD8E0C00CA8CF5 /* RSDatabase.framework */; }; + 51E498A524A8069300B667CB /* RSDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FC020DD8E0C00CA8CF5 /* RSDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E498A624A8069300B667CB /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8C20DD8CF800CA8CF5 /* RSParser.framework */; }; + 51E498A724A8069300B667CB /* RSParser.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8C20DD8CF800CA8CF5 /* RSParser.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E498A824A8069300B667CB /* RSTree.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */; }; + 51E498A924A8069300B667CB /* RSTree.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E498AA24A8069300B667CB /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9F20DD8D0500CA8CF5 /* RSWeb.framework */; }; + 51E498AB24A8069300B667CB /* RSWeb.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9F20DD8D0500CA8CF5 /* RSWeb.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E498AC24A8069300B667CB /* Secrets.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5102FD7B244008A700534F17 /* Secrets.framework */; }; + 51E498AD24A8069300B667CB /* Secrets.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5102FD7B244008A700534F17 /* Secrets.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E498AE24A8069300B667CB /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; }; + 51E498AF24A8069300B667CB /* SyncDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 51E498B124A806A400B667CB /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51E4DAEC2425F6940091EB5B /* CloudKit.framework */; }; + 51E498B324A806AA00B667CB /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51E498B224A806AA00B667CB /* WebKit.framework */; }; + 51E498C724A8085D00B667CB /* StarredFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */; }; + 51E498C824A8085D00B667CB /* SmartFeedsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC88171FE59CBF00644329 /* SmartFeedsController.swift */; }; + 51E498C924A8085D00B667CB /* PseudoFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5351FC22FCB00998D64 /* PseudoFeed.swift */; }; + 51E498CA24A8085D00B667CB /* SmartFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DEE56422C32CA4005FC42C /* SmartFeedDelegate.swift */; }; + 51E498CB24A8085D00B667CB /* TodayFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5361FC22FCB00998D64 /* TodayFeedDelegate.swift */; }; + 51E498CC24A8085D00B667CB /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; }; + 51E498CD24A8085D00B667CB /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; + 51E498CE24A8085D00B667CB /* UnreadFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */; }; + 51E498CF24A8085D00B667CB /* SmartFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7C01FC2488C00854A1F /* SmartFeed.swift */; }; + 51E498F124A8085D00B667CB /* StarredFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */; }; + 51E498F224A8085D00B667CB /* SmartFeedsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC88171FE59CBF00644329 /* SmartFeedsController.swift */; }; + 51E498F324A8085D00B667CB /* PseudoFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5351FC22FCB00998D64 /* PseudoFeed.swift */; }; + 51E498F424A8085D00B667CB /* SmartFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DEE56422C32CA4005FC42C /* SmartFeedDelegate.swift */; }; + 51E498F524A8085D00B667CB /* TodayFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5361FC22FCB00998D64 /* TodayFeedDelegate.swift */; }; + 51E498F624A8085D00B667CB /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; }; + 51E498F724A8085D00B667CB /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; }; + 51E498F824A8085D00B667CB /* UnreadFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */; }; + 51E498F924A8085D00B667CB /* SmartFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7C01FC2488C00854A1F /* SmartFeed.swift */; }; + 51E498FA24A808BA00B667CB /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; }; + 51E498FB24A808BA00B667CB /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; }; + 51E498FC24A808BA00B667CB /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; + 51E498FD24A808BA00B667CB /* ColorHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F78227716380050506E /* ColorHash.swift */; }; + 51E498FE24A808BA00B667CB /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; }; + 51E498FF24A808BB00B667CB /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; }; + 51E4990024A808BB00B667CB /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; }; + 51E4990124A808BB00B667CB /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; }; + 51E4990224A808BB00B667CB /* ColorHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F78227716380050506E /* ColorHash.swift */; }; + 51E4990324A808BB00B667CB /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; }; + 51E4990424A808C300B667CB /* WebFeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */; }; + 51E4990524A808C300B667CB /* FeaturedImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */; }; + 51E4990624A808C300B667CB /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; }; + 51E4990724A808C300B667CB /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; }; + 51E4990824A808C300B667CB /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; }; + 51E4990924A808C500B667CB /* WebFeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */; }; + 51E4990A24A808C500B667CB /* FeaturedImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */; }; + 51E4990B24A808C500B667CB /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; }; + 51E4990C24A808C500B667CB /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; }; + 51E4990D24A808C500B667CB /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; }; + 51E4990E24A808CC00B667CB /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; }; + 51E4990F24A808CC00B667CB /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; }; + 51E4991024A808DE00B667CB /* SmallIconProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */; }; + 51E4991124A808DE00B667CB /* SmallIconProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */; }; + 51E4991224A808FB00B667CB /* AddWebFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */; }; + 51E4991324A808FB00B667CB /* AddWebFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */; }; + 51E4991424A808FF00B667CB /* ArticleStringFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97731ED9EC04007D329B /* ArticleStringFormatter.swift */; }; + 51E4991524A808FF00B667CB /* ArticleStringFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97731ED9EC04007D329B /* ArticleStringFormatter.swift */; }; + 51E4991624A8090300B667CB /* ArticleUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */; }; + 51E4991724A8090400B667CB /* ArticleUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */; }; + 51E4991824A8090A00B667CB /* CacheCleaner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6B52375E612001ABC45 /* CacheCleaner.swift */; }; + 51E4991924A8090A00B667CB /* CacheCleaner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6B52375E612001ABC45 /* CacheCleaner.swift */; }; + 51E4991A24A8090F00B667CB /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; }; + 51E4991B24A8091000B667CB /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; }; + 51E4991C24A8092000B667CB /* NSAttributedString+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */; }; + 51E4991D24A8092100B667CB /* NSAttributedString+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */; }; + 51E4991E24A8094300B667CB /* RSImage-AppIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B8075D239C49D300F191E0 /* RSImage-AppIcons.swift */; }; + 51E4991F24A8094300B667CB /* RSImage-AppIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B8075D239C49D300F191E0 /* RSImage-AppIcons.swift */; }; + 51E4992024A8095000B667CB /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; }; + 51E4992124A8095000B667CB /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; }; + 51E4992224A8095600B667CB /* URL-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */; }; + 51E4992324A8095700B667CB /* URL-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */; }; + 51E4992424A8098400B667CB /* SmartFeedPasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EB92031649C00BC20B7 /* SmartFeedPasteboardWriter.swift */; }; + 51E4992624A80AAB00B667CB /* AppAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4992524A80AAB00B667CB /* AppAssets.swift */; }; + 51E4992724A80AAB00B667CB /* AppAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4992524A80AAB00B667CB /* AppAssets.swift */; }; + 51E4992924A866F000B667CB /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4992824A866F000B667CB /* AppDefaults.swift */; }; + 51E4992A24A866F000B667CB /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4992824A866F000B667CB /* AppDefaults.swift */; }; + 51E4992B24A8676300B667CB /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; }; + 51E4992C24A8676300B667CB /* ArticleSorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */; }; + 51E4992D24A8676300B667CB /* FetchRequestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAFCAE22BC8C35007694F0 /* FetchRequestOperation.swift */; }; + 51E4992E24A8676300B667CB /* FetchRequestQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAFCA322BC8C08007694F0 /* FetchRequestQueue.swift */; }; + 51E4992F24A8676400B667CB /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; }; + 51E4993024A8676400B667CB /* ArticleSorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */; }; + 51E4993124A8676400B667CB /* FetchRequestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAFCAE22BC8C35007694F0 /* FetchRequestOperation.swift */; }; + 51E4993224A8676400B667CB /* FetchRequestQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAFCA322BC8C08007694F0 /* FetchRequestQueue.swift */; }; + 51E4993324A867E700B667CB /* AppNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */; }; + 51E4993424A867E700B667CB /* UserInfoKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9805237DCAC90028BCAA /* UserInfoKey.swift */; }; + 51E4993524A867E800B667CB /* AppNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */; }; + 51E4993624A867E800B667CB /* UserInfoKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9805237DCAC90028BCAA /* UserInfoKey.swift */; }; + 51E4993724A8680E00B667CB /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513228F2233037620033D4ED /* Reachability.swift */; }; + 51E4993824A8680E00B667CB /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513228F2233037620033D4ED /* Reachability.swift */; }; + 51E4993A24A8708800B667CB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4993924A8708800B667CB /* AppDelegate.swift */; }; + 51E4993C24A8709900B667CB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4993B24A8709900B667CB /* AppDelegate.swift */; }; + 51E4993D24A870F800B667CB /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; }; + 51E4993E24A870F900B667CB /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; }; + 51E4993F24A8713B00B667CB /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; }; + 51E4994024A8713B00B667CB /* AccountRefreshTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE7226F68D90010922C /* AccountRefreshTimer.swift */; }; + 51E4994124A8713B00B667CB /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; + 51E4994224A8713C00B667CB /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; }; + 51E4994424A8713C00B667CB /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; }; + 51E4994524A872AD00B667CB /* org.sparkle-project.Downloader.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 65ED42BC235E71B40081F399 /* org.sparkle-project.Downloader.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 51E4994624A872AD00B667CB /* org.sparkle-project.InstallerConnection.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 65ED42B8235E71B40081F399 /* org.sparkle-project.InstallerConnection.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 51E4994724A872AD00B667CB /* org.sparkle-project.InstallerLauncher.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 65ED42B6235E71B40081F399 /* org.sparkle-project.InstallerLauncher.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 51E4994824A872AD00B667CB /* org.sparkle-project.InstallerStatus.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 65ED42BA235E71B40081F399 /* org.sparkle-project.InstallerStatus.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 51E4994A24A8734C00B667CB /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; }; + 51E4994B24A8734C00B667CB /* SendToMicroBlogCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */; }; + 51E4994C24A8734C00B667CB /* RedditFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */; }; + 51E4994D24A8734C00B667CB /* ExtensionPointIdentifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5176243E90200089E588 /* ExtensionPointIdentifer.swift */; }; + 51E4994E24A8734C00B667CB /* SendToMarsEditCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */; }; + 51E4994F24A8734C00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */; }; + 51E4995024A8734C00B667CB /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; }; + 51E4995124A8734D00B667CB /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; }; + 51E4995324A8734D00B667CB /* RedditFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */; }; + 51E4995424A8734D00B667CB /* ExtensionPointIdentifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5176243E90200089E588 /* ExtensionPointIdentifer.swift */; }; + 51E4995624A8734D00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */; }; + 51E4995724A8734D00B667CB /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; }; + 51E4995924A873F900B667CB /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4995824A873F900B667CB /* ErrorHandler.swift */; }; + 51E4995A24A873F900B667CB /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E4995824A873F900B667CB /* ErrorHandler.swift */; }; + 51E4995B24A875D500B667CB /* ArticlePasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */; }; + 51E4995C24A875F300B667CB /* ArticleRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */; }; + 51E4995D24A875F300B667CB /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; + 51E4995E24A875F300B667CB /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; }; + 51E4995F24A875F300B667CB /* shared.css in Resources */ = {isa = PBXBuildFile; fileRef = B27EEBDF244D15F2000932E6 /* shared.css */; }; + 51E4996024A875F300B667CB /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; }; + 51E4996124A875F400B667CB /* ArticleRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */; }; + 51E4996224A875F400B667CB /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; + 51E4996324A875F400B667CB /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; }; + 51E4996424A875F400B667CB /* shared.css in Resources */ = {isa = PBXBuildFile; fileRef = B27EEBDF244D15F2000932E6 /* shared.css */; }; + 51E4996524A875F400B667CB /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; }; + 51E4996624A8760B00B667CB /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; }; + 51E4996724A8760B00B667CB /* ArticleStylesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */; }; + 51E4996824A8760C00B667CB /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; }; + 51E4996924A8760C00B667CB /* ArticleStylesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */; }; + 51E4996A24A8762D00B667CB /* ExtractedArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A62332BE880090D516 /* ExtractedArticle.swift */; }; + 51E4996B24A8762D00B667CB /* ArticleExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A32332BE110090D516 /* ArticleExtractor.swift */; }; + 51E4996C24A8762D00B667CB /* ExtractedArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A62332BE880090D516 /* ExtractedArticle.swift */; }; + 51E4996D24A8762D00B667CB /* ArticleExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A32332BE110090D516 /* ArticleExtractor.swift */; }; + 51E4996E24A8764C00B667CB /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityManager.swift */; }; + 51E4996F24A8764C00B667CB /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; }; + 51E4997024A8764C00B667CB /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityManager.swift */; }; + 51E4997124A8764C00B667CB /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; }; + 51E4997224A8784300B667CB /* DefaultFeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */; }; + 51E4997324A8784300B667CB /* DefaultFeeds.opml in Resources */ = {isa = PBXBuildFile; fileRef = 84A3EE52223B667F00557320 /* DefaultFeeds.opml */; }; + 51E4997424A8784400B667CB /* DefaultFeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */; }; + 51E4997524A8784400B667CB /* DefaultFeeds.opml in Resources */ = {isa = PBXBuildFile; fileRef = 84A3EE52223B667F00557320 /* DefaultFeeds.opml */; }; + 51E4997624A87FFC00B667CB /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65ED42B0235E71B40081F399 /* Sparkle.framework */; }; + 51E4997724A87FFC00B667CB /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 65ED42B0235E71B40081F399 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51E4DAED2425F6940091EB5B /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51E4DAEC2425F6940091EB5B /* CloudKit.framework */; }; 51E4DB082425F9EB0091EB5B /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51E4DB072425F9EB0091EB5B /* CloudKit.framework */; }; 51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; }; @@ -1320,6 +1503,63 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + 51E4989524A8061400B667CB /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 51E4989024A8061400B667CB /* RSWeb.framework in Embed Frameworks */, + 51E4988A24A8061400B667CB /* RSDatabase.framework in Embed Frameworks */, + 51E4988E24A8061400B667CB /* RSTree.framework in Embed Frameworks */, + 51E4988024A8061400B667CB /* Account.framework in Embed Frameworks */, + 51E4988224A8061400B667CB /* Articles.framework in Embed Frameworks */, + 51E4988624A8061400B667CB /* OAuthSwift.framework in Embed Frameworks */, + 51E4989424A8061400B667CB /* SyncDatabase.framework in Embed Frameworks */, + 51E4988824A8061400B667CB /* RSCore.framework in Embed Frameworks */, + 51E4988C24A8061400B667CB /* RSParser.framework in Embed Frameworks */, + 51E4989224A8061400B667CB /* Secrets.framework in Embed Frameworks */, + 51E4988424A8061400B667CB /* ArticlesDatabase.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 51E498B024A8069300B667CB /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 51E498AB24A8069300B667CB /* RSWeb.framework in Embed Frameworks */, + 51E498A524A8069300B667CB /* RSDatabase.framework in Embed Frameworks */, + 51E498A924A8069300B667CB /* RSTree.framework in Embed Frameworks */, + 51E4989B24A8069300B667CB /* Account.framework in Embed Frameworks */, + 51E4989D24A8069300B667CB /* Articles.framework in Embed Frameworks */, + 51E498A124A8069300B667CB /* OAuthSwift.framework in Embed Frameworks */, + 51E498AF24A8069300B667CB /* SyncDatabase.framework in Embed Frameworks */, + 51E4997724A87FFC00B667CB /* Sparkle.framework in Embed Frameworks */, + 51E498A324A8069300B667CB /* RSCore.framework in Embed Frameworks */, + 51E498A724A8069300B667CB /* RSParser.framework in Embed Frameworks */, + 51E498AD24A8069300B667CB /* Secrets.framework in Embed Frameworks */, + 51E4989F24A8069300B667CB /* ArticlesDatabase.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 51E4994924A872AD00B667CB /* Embed XPC Services */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices"; + dstSubfolderSpec = 16; + files = ( + 51E4994824A872AD00B667CB /* org.sparkle-project.InstallerStatus.xpc in Embed XPC Services */, + 51E4994624A872AD00B667CB /* org.sparkle-project.InstallerConnection.xpc in Embed XPC Services */, + 51E4994724A872AD00B667CB /* org.sparkle-project.InstallerLauncher.xpc in Embed XPC Services */, + 51E4994524A872AD00B667CB /* org.sparkle-project.Downloader.xpc in Embed XPC Services */, + ); + name = "Embed XPC Services"; + runOnlyForDeploymentPostprocessing = 0; + }; 6581C75720CED60100F4AD34 /* Embed App Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -1565,7 +1805,7 @@ 51BB7C302335ACDE008E8144 /* page.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = page.html; sourceTree = ""; }; 51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL-Extensions.swift"; sourceTree = ""; }; 51BEB22C2451E8340066DEDD /* TwitterEnterDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterEnterDetailTableViewController.swift; sourceTree = ""; }; - 51C0513624A77DF700194D5E /* NetNewsWire_MultiplatformApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetNewsWire_MultiplatformApp.swift; sourceTree = ""; }; + 51C0513624A77DF700194D5E /* NetNewsWire.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetNewsWire.swift; sourceTree = ""; }; 51C0513724A77DF700194D5E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 51C0513824A77DF800194D5E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 51C0513D24A77DF800194D5E /* NetNewsWire.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NetNewsWire.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1613,6 +1853,14 @@ 51E3EB3C229AB08300645299 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = ""; }; 51E43961238037C400015C31 /* AddWebFeedFolderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedFolderViewController.swift; sourceTree = ""; }; 51E4397F23805EBC00015C31 /* AddComboTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddComboTableViewCell.swift; sourceTree = ""; }; + 51E4989624A8065700B667CB /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/CloudKit.framework; sourceTree = DEVELOPER_DIR; }; + 51E4989824A8067000B667CB /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; }; + 51E498B224A806AA00B667CB /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; + 51E4992524A80AAB00B667CB /* AppAssets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAssets.swift; sourceTree = ""; }; + 51E4992824A866F000B667CB /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDefaults.swift; sourceTree = ""; }; + 51E4993924A8708800B667CB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 51E4993B24A8709900B667CB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 51E4995824A873F900B667CB /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = ""; }; 51E4DAEC2425F6940091EB5B /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; 51E4DB072425F9EB0091EB5B /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.2.sdk/System/Library/Frameworks/CloudKit.framework; sourceTree = DEVELOPER_DIR; }; 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleStatusSyncTimer.swift; sourceTree = ""; }; @@ -1910,6 +2158,19 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 51E4988F24A8061400B667CB /* RSWeb.framework in Frameworks */, + 51E4988924A8061400B667CB /* RSDatabase.framework in Frameworks */, + 51E4988D24A8061400B667CB /* RSTree.framework in Frameworks */, + 51E4987F24A8061400B667CB /* Account.framework in Frameworks */, + 51E4988124A8061400B667CB /* Articles.framework in Frameworks */, + 51E4988524A8061400B667CB /* OAuthSwift.framework in Frameworks */, + 51E4989324A8061400B667CB /* SyncDatabase.framework in Frameworks */, + 51E4988724A8061400B667CB /* RSCore.framework in Frameworks */, + 51E4988B24A8061400B667CB /* RSParser.framework in Frameworks */, + 51E4989724A8065700B667CB /* CloudKit.framework in Frameworks */, + 51E4989124A8061400B667CB /* Secrets.framework in Frameworks */, + 51E4989924A8067000B667CB /* WebKit.framework in Frameworks */, + 51E4988324A8061400B667CB /* ArticlesDatabase.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1917,6 +2178,20 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 51E498AA24A8069300B667CB /* RSWeb.framework in Frameworks */, + 51E498A424A8069300B667CB /* RSDatabase.framework in Frameworks */, + 51E498A824A8069300B667CB /* RSTree.framework in Frameworks */, + 51E4997624A87FFC00B667CB /* Sparkle.framework in Frameworks */, + 51E4989A24A8069300B667CB /* Account.framework in Frameworks */, + 51E4989C24A8069300B667CB /* Articles.framework in Frameworks */, + 51E498A024A8069300B667CB /* OAuthSwift.framework in Frameworks */, + 51E498AE24A8069300B667CB /* SyncDatabase.framework in Frameworks */, + 51E498A224A8069300B667CB /* RSCore.framework in Frameworks */, + 51E498A624A8069300B667CB /* RSParser.framework in Frameworks */, + 51E498B124A806A400B667CB /* CloudKit.framework in Frameworks */, + 51E498AC24A8069300B667CB /* Secrets.framework in Frameworks */, + 51E498B324A806AA00B667CB /* WebKit.framework in Frameworks */, + 51E4989E24A8069300B667CB /* ArticlesDatabase.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2269,6 +2544,7 @@ 51C0513F24A77DF800194D5E /* Info.plist */, 51C051CE24A7A72100194D5E /* iOS.entitlements */, 51C051CF24A7A72100194D5E /* iOS-dev.entitlements */, + 51E4993B24A8709900B667CB /* AppDelegate.swift */, ); path = iOS; sourceTree = ""; @@ -2279,6 +2555,7 @@ 51C0514624A77DF800194D5E /* Info.plist */, 51C0514724A77DF800194D5E /* macOS.entitlements */, 51C051CD24A7A6DB00194D5E /* macOS-dev.entitlements */, + 51E4993924A8708800B667CB /* AppDelegate.swift */, ); path = macOS; sourceTree = ""; @@ -2286,8 +2563,11 @@ 51C0519524A77E8B00194D5E /* Shared */ = { isa = PBXGroup; children = ( - 51C0513624A77DF700194D5E /* NetNewsWire_MultiplatformApp.swift */, + 51C0513624A77DF700194D5E /* NetNewsWire.swift */, 51C0513724A77DF700194D5E /* ContentView.swift */, + 51E4992524A80AAB00B667CB /* AppAssets.swift */, + 51E4992824A866F000B667CB /* AppDefaults.swift */, + 51E4995824A873F900B667CB /* ErrorHandler.swift */, 51C0513824A77DF800194D5E /* Assets.xcassets */, ); path = Shared; @@ -2451,8 +2731,11 @@ 51C452B22265141B00C03939 /* Frameworks */ = { isa = PBXGroup; children = ( + 51E4989824A8067000B667CB /* WebKit.framework */, + 51E498B224A806AA00B667CB /* WebKit.framework */, 51E4DB072425F9EB0091EB5B /* CloudKit.framework */, 51E4DAEC2425F6940091EB5B /* CloudKit.framework */, + 51E4989624A8065700B667CB /* CloudKit.framework */, 51C452B32265141B00C03939 /* WebKit.framework */, ); name = Frameworks; @@ -3276,6 +3559,7 @@ 51C0513924A77DF800194D5E /* Sources */, 51C0513A24A77DF800194D5E /* Frameworks */, 51C0513B24A77DF800194D5E /* Resources */, + 51E4989524A8061400B667CB /* Embed Frameworks */, ); buildRules = ( ); @@ -3293,6 +3577,8 @@ 51C0514024A77DF800194D5E /* Sources */, 51C0514124A77DF800194D5E /* Frameworks */, 51C0514224A77DF800194D5E /* Resources */, + 51E498B024A8069300B667CB /* Embed Frameworks */, + 51E4994924A872AD00B667CB /* Embed XPC Services */, ); buildRules = ( ); @@ -3944,7 +4230,12 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 51E4995F24A875F300B667CB /* shared.css in Resources */, + 51E4997324A8784300B667CB /* DefaultFeeds.opml in Resources */, 51C0516224A77DF800194D5E /* Assets.xcassets in Resources */, + 51E4996024A875F300B667CB /* template.html in Resources */, + 51E4995E24A875F300B667CB /* newsfoot.js in Resources */, + 51E4995D24A875F300B667CB /* main.js in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3952,7 +4243,12 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 51E4996424A875F400B667CB /* shared.css in Resources */, + 51E4997524A8784400B667CB /* DefaultFeeds.opml in Resources */, 51C0516324A77DF800194D5E /* Assets.xcassets in Resources */, + 51E4996524A875F400B667CB /* template.html in Resources */, + 51E4996324A875F400B667CB /* newsfoot.js in Resources */, + 51E4996224A875F400B667CB /* main.js in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4273,8 +4569,65 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 51E4995924A873F900B667CB /* ErrorHandler.swift in Sources */, + 51E4992F24A8676400B667CB /* ArticleArray.swift in Sources */, + 51E4994424A8713C00B667CB /* RefreshInterval.swift in Sources */, + 51E498F824A8085D00B667CB /* UnreadFeed.swift in Sources */, + 51E4996A24A8762D00B667CB /* ExtractedArticle.swift in Sources */, + 51E498F124A8085D00B667CB /* StarredFeedDelegate.swift in Sources */, + 51E498FF24A808BB00B667CB /* SingleFaviconDownloader.swift in Sources */, + 51E4997224A8784300B667CB /* DefaultFeedsImporter.swift in Sources */, + 51E4990D24A808C500B667CB /* RSHTMLMetadata+Extension.swift in Sources */, + 51E4995C24A875F300B667CB /* ArticleRenderer.swift in Sources */, + 51E4992324A8095700B667CB /* URL-Extensions.swift in Sources */, + 51E4993624A867E800B667CB /* UserInfoKey.swift in Sources */, + 51E4990924A808C500B667CB /* WebFeedIconDownloader.swift in Sources */, + 51E498F524A8085D00B667CB /* TodayFeedDelegate.swift in Sources */, + 51E4990B24A808C500B667CB /* ImageDownloader.swift in Sources */, + 51E498F424A8085D00B667CB /* SmartFeedDelegate.swift in Sources */, + 51E4993024A8676400B667CB /* ArticleSorter.swift in Sources */, + 51E4990A24A808C500B667CB /* FeaturedImageDownloader.swift in Sources */, + 51E4993824A8680E00B667CB /* Reachability.swift in Sources */, + 51E4993224A8676400B667CB /* FetchRequestQueue.swift in Sources */, + 51E4991724A8090400B667CB /* ArticleUtilities.swift in Sources */, + 51E4991B24A8091000B667CB /* IconImage.swift in Sources */, + 51E4995424A8734D00B667CB /* ExtensionPointIdentifer.swift in Sources */, 51C0516024A77DF800194D5E /* ContentView.swift in Sources */, - 51C0515E24A77DF800194D5E /* NetNewsWire_MultiplatformApp.swift in Sources */, + 51E4996924A8760C00B667CB /* ArticleStylesManager.swift in Sources */, + 51E498F324A8085D00B667CB /* PseudoFeed.swift in Sources */, + 51E4996B24A8762D00B667CB /* ArticleExtractor.swift in Sources */, + 51E4990124A808BB00B667CB /* FaviconURLFinder.swift in Sources */, + 51E4991D24A8092100B667CB /* NSAttributedString+NetNewsWire.swift in Sources */, + 51E4995324A8734D00B667CB /* RedditFeedProvider-Extensions.swift in Sources */, + 51E4994224A8713C00B667CB /* ArticleStatusSyncTimer.swift in Sources */, + 51E498F624A8085D00B667CB /* SearchFeedDelegate.swift in Sources */, + 51E498F224A8085D00B667CB /* SmartFeedsController.swift in Sources */, + 51E4997024A8764C00B667CB /* ActivityManager.swift in Sources */, + 51E4990F24A808CC00B667CB /* HTMLMetadataDownloader.swift in Sources */, + 51E4993124A8676400B667CB /* FetchRequestOperation.swift in Sources */, + 51E4992624A80AAB00B667CB /* AppAssets.swift in Sources */, + 51E4995624A8734D00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */, + 51E4992924A866F000B667CB /* AppDefaults.swift in Sources */, + 51E4996824A8760C00B667CB /* ArticleStyle.swift in Sources */, + 51E4990024A808BB00B667CB /* FaviconGenerator.swift in Sources */, + 51E4997124A8764C00B667CB /* ActivityType.swift in Sources */, + 51E4991E24A8094300B667CB /* RSImage-AppIcons.swift in Sources */, + 51E4991324A808FB00B667CB /* AddWebFeedDefaultContainer.swift in Sources */, + 51E4993C24A8709900B667CB /* AppDelegate.swift in Sources */, + 51E498F924A8085D00B667CB /* SmartFeed.swift in Sources */, + 51E4995124A8734D00B667CB /* ExtensionPointManager.swift in Sources */, + 51E4990C24A808C500B667CB /* AuthorAvatarDownloader.swift in Sources */, + 51E4992124A8095000B667CB /* RSImage-Extensions.swift in Sources */, + 51E4990324A808BB00B667CB /* FaviconDownloader.swift in Sources */, + 51E4990224A808BB00B667CB /* ColorHash.swift in Sources */, + 51E4991924A8090A00B667CB /* CacheCleaner.swift in Sources */, + 51E498F724A8085D00B667CB /* SearchTimelineFeedDelegate.swift in Sources */, + 51E4993524A867E800B667CB /* AppNotifications.swift in Sources */, + 51C0515E24A77DF800194D5E /* NetNewsWire.swift in Sources */, + 51E4993D24A870F800B667CB /* UserNotificationManager.swift in Sources */, + 51E4991524A808FF00B667CB /* ArticleStringFormatter.swift in Sources */, + 51E4995724A8734D00B667CB /* ExtensionPoint.swift in Sources */, + 51E4991124A808DE00B667CB /* SmallIconProvider.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4282,8 +4635,70 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 51E4993A24A8708800B667CB /* AppDelegate.swift in Sources */, + 51E498CE24A8085D00B667CB /* UnreadFeed.swift in Sources */, + 51E498C724A8085D00B667CB /* StarredFeedDelegate.swift in Sources */, + 51E498FA24A808BA00B667CB /* SingleFaviconDownloader.swift in Sources */, + 51E4993F24A8713B00B667CB /* ArticleStatusSyncTimer.swift in Sources */, + 51E4993724A8680E00B667CB /* Reachability.swift in Sources */, + 51E4994B24A8734C00B667CB /* SendToMicroBlogCommand.swift in Sources */, + 51E4996F24A8764C00B667CB /* ActivityType.swift in Sources */, + 51E4994E24A8734C00B667CB /* SendToMarsEditCommand.swift in Sources */, + 51E4996624A8760B00B667CB /* ArticleStyle.swift in Sources */, + 51E4996C24A8762D00B667CB /* ExtractedArticle.swift in Sources */, + 51E4990824A808C300B667CB /* RSHTMLMetadata+Extension.swift in Sources */, + 51E4992B24A8676300B667CB /* ArticleArray.swift in Sources */, + 51E4994D24A8734C00B667CB /* ExtensionPointIdentifer.swift in Sources */, + 51E4992224A8095600B667CB /* URL-Extensions.swift in Sources */, + 51E4990424A808C300B667CB /* WebFeedIconDownloader.swift in Sources */, + 51E498CB24A8085D00B667CB /* TodayFeedDelegate.swift in Sources */, + 51E4993324A867E700B667CB /* AppNotifications.swift in Sources */, + 51E4990624A808C300B667CB /* ImageDownloader.swift in Sources */, + 51E4994F24A8734C00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */, + 51E498CA24A8085D00B667CB /* SmartFeedDelegate.swift in Sources */, + 51E4990524A808C300B667CB /* FeaturedImageDownloader.swift in Sources */, + 51E4991624A8090300B667CB /* ArticleUtilities.swift in Sources */, + 51E4991A24A8090F00B667CB /* IconImage.swift in Sources */, + 51E4992724A80AAB00B667CB /* AppAssets.swift in Sources */, + 51E4995B24A875D500B667CB /* ArticlePasteboardWriter.swift in Sources */, + 51E4993424A867E700B667CB /* UserInfoKey.swift in Sources */, + 51E4994C24A8734C00B667CB /* RedditFeedProvider-Extensions.swift in Sources */, + 51E4994124A8713B00B667CB /* RefreshInterval.swift in Sources */, 51C0516124A77DF800194D5E /* ContentView.swift in Sources */, - 51C0515F24A77DF800194D5E /* NetNewsWire_MultiplatformApp.swift in Sources */, + 51E498C924A8085D00B667CB /* PseudoFeed.swift in Sources */, + 51E498FC24A808BA00B667CB /* FaviconURLFinder.swift in Sources */, + 51E4991C24A8092000B667CB /* NSAttributedString+NetNewsWire.swift in Sources */, + 51E4994A24A8734C00B667CB /* ExtensionPointManager.swift in Sources */, + 51E4996D24A8762D00B667CB /* ArticleExtractor.swift in Sources */, + 51E4994024A8713B00B667CB /* AccountRefreshTimer.swift in Sources */, + 51E498CC24A8085D00B667CB /* SearchFeedDelegate.swift in Sources */, + 51E498C824A8085D00B667CB /* SmartFeedsController.swift in Sources */, + 51E4992C24A8676300B667CB /* ArticleSorter.swift in Sources */, + 51E4995024A8734C00B667CB /* ExtensionPoint.swift in Sources */, + 51E4990E24A808CC00B667CB /* HTMLMetadataDownloader.swift in Sources */, + 51E498FB24A808BA00B667CB /* FaviconGenerator.swift in Sources */, + 51E4996724A8760B00B667CB /* ArticleStylesManager.swift in Sources */, + 51E4996E24A8764C00B667CB /* ActivityManager.swift in Sources */, + 51E4995A24A873F900B667CB /* ErrorHandler.swift in Sources */, + 51E4991F24A8094300B667CB /* RSImage-AppIcons.swift in Sources */, + 51E4991224A808FB00B667CB /* AddWebFeedDefaultContainer.swift in Sources */, + 51E4993E24A870F900B667CB /* UserNotificationManager.swift in Sources */, + 51E4992E24A8676300B667CB /* FetchRequestQueue.swift in Sources */, + 51E498CF24A8085D00B667CB /* SmartFeed.swift in Sources */, + 51E4990724A808C300B667CB /* AuthorAvatarDownloader.swift in Sources */, + 51E4997424A8784400B667CB /* DefaultFeedsImporter.swift in Sources */, + 51E4992024A8095000B667CB /* RSImage-Extensions.swift in Sources */, + 51E498FE24A808BA00B667CB /* FaviconDownloader.swift in Sources */, + 51E498FD24A808BA00B667CB /* ColorHash.swift in Sources */, + 51E4992A24A866F000B667CB /* AppDefaults.swift in Sources */, + 51E4991824A8090A00B667CB /* CacheCleaner.swift in Sources */, + 51E498CD24A8085D00B667CB /* SearchTimelineFeedDelegate.swift in Sources */, + 51E4996124A875F400B667CB /* ArticleRenderer.swift in Sources */, + 51C0515F24A77DF800194D5E /* NetNewsWire.swift in Sources */, + 51E4992D24A8676300B667CB /* FetchRequestOperation.swift in Sources */, + 51E4992424A8098400B667CB /* SmartFeedPasteboardWriter.swift in Sources */, + 51E4991424A808FF00B667CB /* ArticleStringFormatter.swift in Sources */, + 51E4991024A808DE00B667CB /* SmallIconProvider.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Shared/Favicons/SingleFaviconDownloader.swift b/Shared/Favicons/SingleFaviconDownloader.swift index 9ef2b61df..1b82802f6 100644 --- a/Shared/Favicons/SingleFaviconDownloader.swift +++ b/Shared/Favicons/SingleFaviconDownloader.swift @@ -7,6 +7,7 @@ // import Foundation +import os.log import RSCore import RSWeb @@ -28,6 +29,8 @@ final class SingleFaviconDownloader { var iconImage: IconImage? let homePageURL: String? + private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "SingleFaviconDownloader") + private var lastDownloadAttemptDate: Date private var diskStatus = DiskStatus.unknown private var diskCache: BinaryDiskCache @@ -145,7 +148,7 @@ private extension SingleFaviconDownloader { } if let error = error { - appDelegate.logMessage("Error downloading favicon at \(url): \(error)", type: .warning) + os_log(.info, log: self.log, "Error downloading image at %@: %@.", url.absoluteString, error.localizedDescription) } completion(nil) diff --git a/Shared/Images/ImageDownloader.swift b/Shared/Images/ImageDownloader.swift index 7ea33c0ac..9a549d3ef 100644 --- a/Shared/Images/ImageDownloader.swift +++ b/Shared/Images/ImageDownloader.swift @@ -7,6 +7,7 @@ // import Foundation +import os.log import RSCore import RSWeb @@ -17,6 +18,8 @@ extension Notification.Name { final class ImageDownloader { + private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "ImageDownloader") + private let folder: String private var diskCache: BinaryDiskCache private let queue: DispatchQueue @@ -112,7 +115,7 @@ private extension ImageDownloader { self.badURLs.insert(url) } if let error = error { - appDelegate.logMessage("Error downloading image at \(url): \(error)", type: .warning) + os_log(.info, log: self.log, "Error downloading image at %@: %@.", url, error.localizedDescription) } completion(nil) diff --git a/Shared/Importers/DefaultFeedsImporter.swift b/Shared/Importers/DefaultFeedsImporter.swift index b86d39e36..8ed67d3b9 100644 --- a/Shared/Importers/DefaultFeedsImporter.swift +++ b/Shared/Importers/DefaultFeedsImporter.swift @@ -13,9 +13,9 @@ import RSCore struct DefaultFeedsImporter { static func importDefaultFeeds(account: Account) { - appDelegate.logDebugMessage("Importing default feeds.") let defaultFeedsURL = Bundle.main.url(forResource: "DefaultFeeds", withExtension: "opml")! AccountManager.shared.defaultAccount.importOPML(defaultFeedsURL) { result in } } + } diff --git a/Shared/SmartFeeds/PseudoFeed.swift b/Shared/SmartFeeds/PseudoFeed.swift index 5c0dc0660..2fd0d9d6c 100644 --- a/Shared/SmartFeeds/PseudoFeed.swift +++ b/Shared/SmartFeeds/PseudoFeed.swift @@ -17,16 +17,6 @@ protocol PseudoFeed: class, Feed, SmallIconProvider, PasteboardWriterOwner { } -private var smartFeedIcon: RSImage = { - return RSImage(named: NSImage.smartBadgeTemplateName)! -}() - -extension PseudoFeed { - - var smallIcon: RSImage? { - return smartFeedIcon - } -} #else import UIKit @@ -38,14 +28,10 @@ protocol PseudoFeed: class, Feed, SmallIconProvider { } -private var smartFeedIcon: UIImage = { - return AppAssets.smartFeedImage -}() +#endif extension PseudoFeed { - var smallIcon: UIImage? { - return smartFeedIcon + var smallIcon: RSImage? { + return AppAssets.smartFeedImage } } - -#endif