Make multiplatform compile shared app resources

This commit is contained in:
Maurice Parker 2020-06-28 03:05:01 -05:00
parent 7ceaa305e7
commit 67c0e00957
44 changed files with 1907 additions and 28 deletions

View File

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

View File

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

View File

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

View File

@ -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"
}
],

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "icloud.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "outline-512.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -0,0 +1,16 @@
{
"images" : [
{
"filename" : "accountFeedbin.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "accountFeedly.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "accountFreshRSS.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "localAccountMac.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@ -0,0 +1,16 @@
{
"images" : [
{
"filename" : "localAccountPad.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}

View File

@ -0,0 +1,16 @@
{
"images" : [
{
"filename" : "localAccountPhone.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "newsblur-512.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "MarsEditOfficial.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "micro-dot-blog.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

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

View File

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "twitter.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "faviconTemplateImage.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

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

View File

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

View File

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

View File

@ -46,5 +46,40 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.ranchero.NetNewsWire.FeedRefresh</string>
</array>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Grant permission to save images from the article.</string>
<key>NSUserActivityTypes</key>
<array>
<string>AddWebFeedIntent</string>
<string>NextUnread</string>
<string>ReadArticle</string>
<string>Restoration</string>
<string>SelectFeed</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
<string>remote-notification</string>
</array>
<key>UserAgent</key>
<string>NetNewsWire (RSS Reader; https://ranchero.com/netnewswire/)</string>
<key>OrganizationIdentifier</key>
<string>$(ORGANIZATION_IDENTIFIER)</string>
<key>DeveloperEntitlements</key>
<string>$(DEVELOPER_ENTITLEMENTS)</string>
<key>AppGroup</key>
<string>group.$(ORGANIZATION_IDENTIFIER).NetNewsWire.iOS</string>
<key>AppIdentifierPrefix</key>
<string>$(AppIdentifierPrefix)</string>
</dict>
</plist>

View File

@ -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() {
// Its possible theres 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 ?? []
// }
//}

View File

@ -24,5 +24,16 @@
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2020 Ranchero Software. All rights reserved.</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UserAgent</key>
<string>NetNewsWire (RSS Reader; https://ranchero.com/netnewswire/)</string>
<key>OrganizationIdentifier</key>
<string>$(ORGANIZATION_IDENTIFIER)</string>
<key>DeveloperEntitlements</key>
<string>$(DEVELOPER_ENTITLEMENTS)</string>
</dict>
</plist>

View File

@ -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 = "<group>"; };
51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL-Extensions.swift"; sourceTree = "<group>"; };
51BEB22C2451E8340066DEDD /* TwitterEnterDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwitterEnterDetailTableViewController.swift; sourceTree = "<group>"; };
51C0513624A77DF700194D5E /* NetNewsWire_MultiplatformApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetNewsWire_MultiplatformApp.swift; sourceTree = "<group>"; };
51C0513624A77DF700194D5E /* NetNewsWire.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetNewsWire.swift; sourceTree = "<group>"; };
51C0513724A77DF700194D5E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
51C0513824A77DF800194D5E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
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 = "<group>"; };
51E43961238037C400015C31 /* AddWebFeedFolderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedFolderViewController.swift; sourceTree = "<group>"; };
51E4397F23805EBC00015C31 /* AddComboTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddComboTableViewCell.swift; sourceTree = "<group>"; };
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 = "<group>"; };
51E4992824A866F000B667CB /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDefaults.swift; sourceTree = "<group>"; };
51E4993924A8708800B667CB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
51E4993B24A8709900B667CB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
51E4995824A873F900B667CB /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = "<group>"; };
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 = "<group>"; };
@ -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 = "<group>";
@ -2279,6 +2555,7 @@
51C0514624A77DF800194D5E /* Info.plist */,
51C0514724A77DF800194D5E /* macOS.entitlements */,
51C051CD24A7A6DB00194D5E /* macOS-dev.entitlements */,
51E4993924A8708800B667CB /* AppDelegate.swift */,
);
path = macOS;
sourceTree = "<group>";
@ -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;
};

View File

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

View File

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

View File

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

View File

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