Compare commits
7 Commits
197edc5e1a
...
991a106907
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 991a106907 | |
Shadowfacts | 8db60a313e | |
Shadowfacts | 1e7e8b7f85 | |
Shadowfacts | 14dca95610 | |
Shadowfacts | 5d625ca844 | |
Shadowfacts | 1dc9903e39 | |
Shadowfacts | b3522a76e1 |
|
@ -0,0 +1,9 @@
|
||||||
|
.DS_Store
|
||||||
|
/.build
|
||||||
|
/Packages
|
||||||
|
/*.xcodeproj
|
||||||
|
xcuserdata/
|
||||||
|
DerivedData/
|
||||||
|
.swiftpm/config/registries.json
|
||||||
|
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||||
|
.netrc
|
|
@ -1,18 +0,0 @@
|
||||||
//
|
|
||||||
// Fervor.h
|
|
||||||
// Fervor
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 10/29/21.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
//! Project version number for Fervor.
|
|
||||||
FOUNDATION_EXPORT double FervorVersionNumber;
|
|
||||||
|
|
||||||
//! Project version string for Fervor.
|
|
||||||
FOUNDATION_EXPORT const unsigned char FervorVersionString[];
|
|
||||||
|
|
||||||
// In this header, you should import all the public headers of your framework using statements like #import <Fervor/PublicHeader.h>
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
// swift-tools-version: 5.7
|
||||||
|
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "Fervor",
|
||||||
|
platforms: [
|
||||||
|
.iOS(.v15),
|
||||||
|
],
|
||||||
|
products: [
|
||||||
|
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||||
|
.library(
|
||||||
|
name: "Fervor",
|
||||||
|
targets: ["Fervor"]),
|
||||||
|
],
|
||||||
|
dependencies: [
|
||||||
|
// Dependencies declare other packages that this package depends on.
|
||||||
|
// .package(url: /* package url */, from: "1.0.0"),
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||||
|
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||||
|
.target(
|
||||||
|
name: "Fervor",
|
||||||
|
dependencies: []),
|
||||||
|
.testTarget(
|
||||||
|
name: "FervorTests",
|
||||||
|
dependencies: ["Fervor"]),
|
||||||
|
]
|
||||||
|
)
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Fervor
|
||||||
|
|
||||||
|
A description of this package.
|
|
@ -0,0 +1,9 @@
|
||||||
|
.DS_Store
|
||||||
|
/.build
|
||||||
|
/Packages
|
||||||
|
/*.xcodeproj
|
||||||
|
xcuserdata/
|
||||||
|
DerivedData/
|
||||||
|
.swiftpm/config/registries.json
|
||||||
|
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||||
|
.netrc
|
|
@ -0,0 +1,34 @@
|
||||||
|
// swift-tools-version: 5.7
|
||||||
|
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "Persistence",
|
||||||
|
platforms: [
|
||||||
|
.iOS(.v15),
|
||||||
|
],
|
||||||
|
products: [
|
||||||
|
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||||
|
.library(
|
||||||
|
name: "Persistence",
|
||||||
|
targets: ["Persistence"]),
|
||||||
|
],
|
||||||
|
dependencies: [
|
||||||
|
// Dependencies declare other packages that this package depends on.
|
||||||
|
// .package(url: /* package url */, from: "1.0.0"),
|
||||||
|
.package(path: "../Fervor"),
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||||
|
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||||
|
.target(
|
||||||
|
name: "Persistence",
|
||||||
|
dependencies: [
|
||||||
|
"Fervor",
|
||||||
|
]),
|
||||||
|
.testTarget(
|
||||||
|
name: "PersistenceTests",
|
||||||
|
dependencies: ["Persistence"]),
|
||||||
|
]
|
||||||
|
)
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Persistence
|
||||||
|
|
||||||
|
A description of this package.
|
|
@ -13,7 +13,7 @@ import Fervor
|
||||||
@objc(Feed)
|
@objc(Feed)
|
||||||
public class Feed: NSManagedObject {
|
public class Feed: NSManagedObject {
|
||||||
|
|
||||||
func updateFromServer(_ serverFeed: Fervor.Feed) {
|
public func updateFromServer(_ serverFeed: Fervor.Feed) {
|
||||||
guard self.id == nil || self.id == serverFeed.id else { return }
|
guard self.id == nil || self.id == serverFeed.id else { return }
|
||||||
self.id = serverFeed.id
|
self.id = serverFeed.id
|
||||||
self.title = serverFeed.title
|
self.title = serverFeed.title
|
|
@ -13,12 +13,11 @@ import Fervor
|
||||||
@objc(Group)
|
@objc(Group)
|
||||||
public class Group: NSManagedObject {
|
public class Group: NSManagedObject {
|
||||||
|
|
||||||
func updateFromServer(_ serverGroup: Fervor.Group) {
|
public func updateFromServer(_ serverGroup: Fervor.Group) {
|
||||||
guard self.id == nil || self.id == serverGroup.id else { return }
|
guard self.id == nil || self.id == serverGroup.id else { return }
|
||||||
self.id = serverGroup.id
|
self.id = serverGroup.id
|
||||||
self.title = serverGroup.title
|
self.title = serverGroup.title
|
||||||
// feeds relationships will be updated after feeds are created in PersistentContainer.sync
|
// feeds relationships will be updated after feeds are created in PersistentContainer.sync
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -13,7 +13,7 @@ import Fervor
|
||||||
@objc(Item)
|
@objc(Item)
|
||||||
public class Item: NSManagedObject {
|
public class Item: NSManagedObject {
|
||||||
|
|
||||||
func updateFromServer(_ serverItem: Fervor.Item) {
|
public func updateFromServer(_ serverItem: Fervor.Item) {
|
||||||
guard self.id == nil || self.id == serverItem.id else { return }
|
guard self.id == nil || self.id == serverItem.id else { return }
|
||||||
self.id = serverItem.id
|
self.id = serverItem.id
|
||||||
self.author = serverItem.author
|
self.author = serverItem.author
|
||||||
|
@ -29,5 +29,4 @@ public class Item: NSManagedObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -9,16 +9,18 @@ import Foundation
|
||||||
import Fervor
|
import Fervor
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
|
||||||
struct LocalData {
|
public struct LocalData {
|
||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
private static let encoder = JSONEncoder()
|
private static let encoder = JSONEncoder()
|
||||||
private static let decoder = JSONDecoder()
|
private static let decoder = JSONDecoder()
|
||||||
|
|
||||||
static var accounts: [Account] {
|
public static var defaults = UserDefaults(suiteName: "group.net.shadowfacts.Reader")!
|
||||||
|
|
||||||
|
public static var accounts: [Account] {
|
||||||
get {
|
get {
|
||||||
guard let data = UserDefaults.standard.data(forKey: "accounts"),
|
guard let data = defaults.data(forKey: "accounts"),
|
||||||
let accounts = try? decoder.decode([Account].self, from: data) else {
|
let accounts = try? decoder.decode([Account].self, from: data) else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
@ -26,38 +28,44 @@ struct LocalData {
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
let data = try! encoder.encode(newValue)
|
let data = try! encoder.encode(newValue)
|
||||||
UserDefaults.standard.set(data, forKey: "accounts")
|
defaults.set(data, forKey: "accounts")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static var mostRecentAccountID: Data? {
|
public static var mostRecentAccountID: Data? {
|
||||||
get {
|
get {
|
||||||
return UserDefaults.standard.data(forKey: "mostRecentAccountID")
|
return defaults.data(forKey: "mostRecentAccountID")
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
UserDefaults.standard.set(newValue, forKey: "mostRecentAccountID")
|
defaults.set(newValue, forKey: "mostRecentAccountID")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func mostRecentAccount() -> Account? {
|
public static func mostRecentAccount() -> Account? {
|
||||||
guard let id = mostRecentAccountID else {
|
guard let id = mostRecentAccountID else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return account(with: id)
|
return account(with: id)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func account(with id: Data) -> Account? {
|
public static func account(with id: Data) -> Account? {
|
||||||
return accounts.first(where: { $0.id == id })
|
return accounts.first(where: { $0.id == id })
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Account: Codable {
|
public struct Account: Codable {
|
||||||
let id: Data
|
public let id: Data
|
||||||
let instanceURL: URL
|
public let instanceURL: URL
|
||||||
let clientID: String
|
public let clientID: String
|
||||||
let clientSecret: String
|
public let clientSecret: String
|
||||||
let token: Token
|
public let token: Token
|
||||||
|
|
||||||
init(instanceURL: URL, clientID: String, clientSecret: String, token: Token) {
|
/// A filename-safe string for this account
|
||||||
|
public var persistenceKey: String {
|
||||||
|
// slashes the base64 string turn into subdirectories which we don't want
|
||||||
|
id.base64EncodedString().replacingOccurrences(of: "/", with: "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(instanceURL: URL, clientID: String, clientSecret: String, token: Token) {
|
||||||
// we use a hash of instance host and account id rather than random ids so that
|
// we use a hash of instance host and account id rather than random ids so that
|
||||||
// user activites can uniquely identify accounts across devices
|
// user activites can uniquely identify accounts across devices
|
||||||
var hasher = SHA256()
|
var hasher = SHA256()
|
|
@ -10,10 +10,10 @@ import Fervor
|
||||||
import OSLog
|
import OSLog
|
||||||
|
|
||||||
// todo: is this actually sendable?
|
// todo: is this actually sendable?
|
||||||
class PersistentContainer: NSPersistentContainer, @unchecked Sendable {
|
public class PersistentContainer: NSPersistentContainer, @unchecked Sendable {
|
||||||
|
|
||||||
private static let managedObjectModel: NSManagedObjectModel = {
|
private static let managedObjectModel: NSManagedObjectModel = {
|
||||||
let url = Bundle.main.url(forResource: "Reader", withExtension: "momd")!
|
let url = Bundle.module.url(forResource: "Reader", withExtension: "momd")!
|
||||||
return NSManagedObjectModel(contentsOf: url)!
|
return NSManagedObjectModel(contentsOf: url)!
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -24,15 +24,38 @@ class PersistentContainer: NSPersistentContainer, @unchecked Sendable {
|
||||||
return context
|
return context
|
||||||
}()
|
}()
|
||||||
|
|
||||||
weak var fervorController: FervorController?
|
|
||||||
|
|
||||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PersistentContainer")
|
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PersistentContainer")
|
||||||
|
|
||||||
init(account: LocalData.Account) {
|
public init(account: LocalData.Account) {
|
||||||
// slashes the base64 string turn into subdirectories which we don't want
|
super.init(name: account.persistenceKey, managedObjectModel: PersistentContainer.managedObjectModel)
|
||||||
let name = account.id.base64EncodedString().replacingOccurrences(of: "/", with: "_")
|
|
||||||
super.init(name: name, managedObjectModel: PersistentContainer.managedObjectModel)
|
|
||||||
|
|
||||||
|
let groupContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.net.shadowfacts.Reader")!
|
||||||
|
let containerAppSupportURL = groupContainerURL
|
||||||
|
.appendingPathComponent("Library", isDirectory: true)
|
||||||
|
.appendingPathComponent("Application Support", isDirectory: true)
|
||||||
|
try! FileManager.default.createDirectory(at: containerAppSupportURL, withIntermediateDirectories: true)
|
||||||
|
|
||||||
|
let containerStoreURL = containerAppSupportURL
|
||||||
|
.appendingPathComponent(name)
|
||||||
|
.appendingPathExtension("sqlite")
|
||||||
|
|
||||||
|
if let existingAppSupport = try? FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
|
||||||
|
let existingStore = existingAppSupport
|
||||||
|
.appendingPathComponent(name)
|
||||||
|
.appendingPathExtension("sqlite")
|
||||||
|
.relativePath
|
||||||
|
.removingPercentEncoding!
|
||||||
|
if FileManager.default.fileExists(atPath: existingStore) {
|
||||||
|
for ext in ["", "-shm", "-wal"] {
|
||||||
|
try! FileManager.default.moveItem(atPath: existingStore + ext, toPath: containerStoreURL.relativePath + ext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let desc = NSPersistentStoreDescription(url: containerStoreURL)
|
||||||
|
desc.type = NSSQLiteStoreType
|
||||||
|
|
||||||
|
persistentStoreDescriptions = [desc]
|
||||||
loadPersistentStores { description, error in
|
loadPersistentStores { description, error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
fatalError("Unable to load persistent store: \(error)")
|
fatalError("Unable to load persistent store: \(error)")
|
||||||
|
@ -41,20 +64,20 @@ class PersistentContainer: NSPersistentContainer, @unchecked Sendable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func saveViewContext() throws {
|
public func saveViewContext() throws {
|
||||||
if viewContext.hasChanges {
|
if viewContext.hasChanges {
|
||||||
try viewContext.save()
|
try viewContext.save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func lastSyncDate() async throws -> Date? {
|
public func lastSyncDate() async throws -> Date? {
|
||||||
return try await backgroundContext.perform {
|
return try await backgroundContext.perform {
|
||||||
let state = try self.backgroundContext.fetch(SyncState.fetchRequest()).first
|
let state = try self.backgroundContext.fetch(SyncState.fetchRequest()).first
|
||||||
return state?.lastSync
|
return state?.lastSync
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateLastSyncDate(_ date: Date) async throws {
|
public func updateLastSyncDate(_ date: Date) async throws {
|
||||||
try await backgroundContext.perform {
|
try await backgroundContext.perform {
|
||||||
if let state = try self.backgroundContext.fetch(SyncState.fetchRequest()).first {
|
if let state = try self.backgroundContext.fetch(SyncState.fetchRequest()).first {
|
||||||
state.lastSync = date
|
state.lastSync = date
|
||||||
|
@ -68,7 +91,7 @@ class PersistentContainer: NSPersistentContainer, @unchecked Sendable {
|
||||||
try await self.saveViewContext()
|
try await self.saveViewContext()
|
||||||
}
|
}
|
||||||
|
|
||||||
func sync(serverGroups: [Fervor.Group], serverFeeds: [Fervor.Feed]) async throws {
|
public func sync(serverGroups: [Fervor.Group], serverFeeds: [Fervor.Feed]) async throws {
|
||||||
try await backgroundContext.perform {
|
try await backgroundContext.perform {
|
||||||
let existingGroups = try self.backgroundContext.fetch(Group.fetchRequest())
|
let existingGroups = try self.backgroundContext.fetch(Group.fetchRequest())
|
||||||
for group in serverGroups {
|
for group in serverGroups {
|
||||||
|
@ -103,7 +126,7 @@ class PersistentContainer: NSPersistentContainer, @unchecked Sendable {
|
||||||
try await self.saveViewContext()
|
try await self.saveViewContext()
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncItems(_ syncUpdate: ItemsSyncUpdate, setSyncState: @escaping (FervorController.SyncState) -> Void) async throws {
|
public func syncItems(_ syncUpdate: ItemsSyncUpdate, setProgress: @escaping (_ current: Int, _ total: Int) -> Void) async throws {
|
||||||
try await backgroundContext.perform {
|
try await backgroundContext.perform {
|
||||||
self.logger.debug("syncItems: deleting \(syncUpdate.delete.count, privacy: .public) items")
|
self.logger.debug("syncItems: deleting \(syncUpdate.delete.count, privacy: .public) items")
|
||||||
let deleteReq = Item.fetchRequest()
|
let deleteReq = Item.fetchRequest()
|
||||||
|
@ -124,7 +147,7 @@ class PersistentContainer: NSPersistentContainer, @unchecked Sendable {
|
||||||
self.logger.debug("syncItems: updating \(existing.count, privacy: .public) items, inserting \(syncUpdate.upsert.count - existing.count, privacy: .public)")
|
self.logger.debug("syncItems: updating \(existing.count, privacy: .public) items, inserting \(syncUpdate.upsert.count - existing.count, privacy: .public)")
|
||||||
// todo: this feels like it'll be slow when there are many items
|
// todo: this feels like it'll be slow when there are many items
|
||||||
for (index, item) in syncUpdate.upsert.enumerated() {
|
for (index, item) in syncUpdate.upsert.enumerated() {
|
||||||
setSyncState(.updateItems(current: index, total: syncUpdate.upsert.count))
|
setProgress(index, syncUpdate.upsert.count)
|
||||||
if let existing = existing.first(where: { $0.id == item.id }) {
|
if let existing = existing.first(where: { $0.id == item.id }) {
|
||||||
existing.updateFromServer(item)
|
existing.updateFromServer(item)
|
||||||
} else {
|
} else {
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19574" systemVersion="21C52" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21223.11" systemVersion="22A5266r" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||||
<entity name="Feed" representedClassName="Feed" syncable="YES">
|
<entity name="Feed" representedClassName="Feed" syncable="YES">
|
||||||
<attribute name="id" attributeType="String"/>
|
<attribute name="id" attributeType="String"/>
|
||||||
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
@ -29,6 +29,7 @@
|
||||||
<entity name="SyncState" representedClassName="SyncState" syncable="YES" codeGenerationType="class">
|
<entity name="SyncState" representedClassName="SyncState" syncable="YES" codeGenerationType="class">
|
||||||
<attribute name="lastSync" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="lastSync" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
</entity>
|
</entity>
|
||||||
|
<fetchRequest name="FetchRequest" entity="Item" predicateString="TRUEPREDICATE"/>
|
||||||
<elements>
|
<elements>
|
||||||
<element name="Feed" positionX="-54" positionY="9" width="128" height="119"/>
|
<element name="Feed" positionX="-54" positionY="9" width="128" height="119"/>
|
||||||
<element name="Group" positionX="-63" positionY="-18" width="128" height="74"/>
|
<element name="Group" positionX="-63" positionY="-18" width="128" height="74"/>
|
|
@ -9,12 +9,7 @@
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
D608238D27DE729E00D7D5F9 /* ItemListType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D608238C27DE729E00D7D5F9 /* ItemListType.swift */; };
|
D608238D27DE729E00D7D5F9 /* ItemListType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D608238C27DE729E00D7D5F9 /* ItemListType.swift */; };
|
||||||
D65B18B22750469D004A9448 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18B12750469D004A9448 /* LoginViewController.swift */; };
|
D65B18B22750469D004A9448 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18B12750469D004A9448 /* LoginViewController.swift */; };
|
||||||
D65B18B4275048D9004A9448 /* ClientRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18B3275048D9004A9448 /* ClientRegistration.swift */; };
|
|
||||||
D65B18B627504920004A9448 /* FervorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18B527504920004A9448 /* FervorController.swift */; };
|
D65B18B627504920004A9448 /* FervorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18B527504920004A9448 /* FervorController.swift */; };
|
||||||
D65B18B82750495D004A9448 /* FervorClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18B72750495D004A9448 /* FervorClient.swift */; };
|
|
||||||
D65B18BA27504A21004A9448 /* URLRequest+Body.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18B927504A21004A9448 /* URLRequest+Body.swift */; };
|
|
||||||
D65B18BC27504FE7004A9448 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18BB27504FE7004A9448 /* Token.swift */; };
|
|
||||||
D65B18BE275051A1004A9448 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18BD275051A1004A9448 /* LocalData.swift */; };
|
|
||||||
D65B18C127505348004A9448 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18C027505348004A9448 /* HomeViewController.swift */; };
|
D65B18C127505348004A9448 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18C027505348004A9448 /* HomeViewController.swift */; };
|
||||||
D68408E827947D0800E327D2 /* PrefsSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68408E727947D0800E327D2 /* PrefsSceneDelegate.swift */; };
|
D68408E827947D0800E327D2 /* PrefsSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68408E727947D0800E327D2 /* PrefsSceneDelegate.swift */; };
|
||||||
D68408ED2794803D00E327D2 /* PrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68408EC2794803D00E327D2 /* PrefsView.swift */; };
|
D68408ED2794803D00E327D2 /* PrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68408EC2794803D00E327D2 /* PrefsView.swift */; };
|
||||||
|
@ -25,31 +20,17 @@
|
||||||
D68B303D2792204B00E8B3FA /* read.js in Resources */ = {isa = PBXBuildFile; fileRef = D68B303C2792204B00E8B3FA /* read.js */; };
|
D68B303D2792204B00E8B3FA /* read.js in Resources */ = {isa = PBXBuildFile; fileRef = D68B303C2792204B00E8B3FA /* read.js */; };
|
||||||
D68B30402792729A00E8B3FA /* AppSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68B303F2792729A00E8B3FA /* AppSplitViewController.swift */; };
|
D68B30402792729A00E8B3FA /* AppSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68B303F2792729A00E8B3FA /* AppSplitViewController.swift */; };
|
||||||
D68B304227932ED500E8B3FA /* UserActivities.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68B304127932ED500E8B3FA /* UserActivities.swift */; };
|
D68B304227932ED500E8B3FA /* UserActivities.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68B304127932ED500E8B3FA /* UserActivities.swift */; };
|
||||||
D6A8A33427766C2800CCEC72 /* PersistentContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A8A33327766C2800CCEC72 /* PersistentContainer.swift */; };
|
|
||||||
D6C687EC272CD27600874C10 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C687EB272CD27600874C10 /* AppDelegate.swift */; };
|
D6C687EC272CD27600874C10 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C687EB272CD27600874C10 /* AppDelegate.swift */; };
|
||||||
D6C687EE272CD27600874C10 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C687ED272CD27600874C10 /* SceneDelegate.swift */; };
|
D6C687EE272CD27600874C10 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C687ED272CD27600874C10 /* SceneDelegate.swift */; };
|
||||||
D6C687F6272CD27600874C10 /* Reader.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = D6C687F4272CD27600874C10 /* Reader.xcdatamodeld */; };
|
|
||||||
D6C687F8272CD27700874C10 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6C687F7272CD27700874C10 /* Assets.xcassets */; };
|
D6C687F8272CD27700874C10 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6C687F7272CD27700874C10 /* Assets.xcassets */; };
|
||||||
D6C687FB272CD27700874C10 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6C687F9272CD27700874C10 /* LaunchScreen.storyboard */; };
|
D6C687FB272CD27700874C10 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6C687F9272CD27700874C10 /* LaunchScreen.storyboard */; };
|
||||||
D6C68806272CD27700874C10 /* ReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C68805272CD27700874C10 /* ReaderTests.swift */; };
|
D6C68806272CD27700874C10 /* ReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C68805272CD27700874C10 /* ReaderTests.swift */; };
|
||||||
D6C68810272CD27700874C10 /* ReaderUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C6880F272CD27700874C10 /* ReaderUITests.swift */; };
|
D6C68810272CD27700874C10 /* ReaderUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C6880F272CD27700874C10 /* ReaderUITests.swift */; };
|
||||||
D6C68812272CD27700874C10 /* ReaderUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C68811272CD27700874C10 /* ReaderUITestsLaunchTests.swift */; };
|
D6C68812272CD27700874C10 /* ReaderUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C68811272CD27700874C10 /* ReaderUITestsLaunchTests.swift */; };
|
||||||
D6C68826272CD2BA00874C10 /* Fervor.h in Headers */ = {isa = PBXBuildFile; fileRef = D6C68825272CD2BA00874C10 /* Fervor.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
D6D5FA24285FE9DB00BBF188 /* WidgetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D5FA23285FE9DB00BBF188 /* WidgetHelper.swift */; };
|
||||||
D6C68829272CD2BA00874C10 /* Fervor.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6C68823272CD2BA00874C10 /* Fervor.framework */; };
|
D6D5FA27285FEA6900BBF188 /* WidgetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D5FA26285FEA6900BBF188 /* WidgetData.swift */; };
|
||||||
D6C6882A272CD2BA00874C10 /* Fervor.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6C68823272CD2BA00874C10 /* Fervor.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
D6D5FA28285FEA8A00BBF188 /* WidgetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D5FA26285FEA6900BBF188 /* WidgetData.swift */; };
|
||||||
D6C68830272CD2CF00874C10 /* Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C6882F272CD2CF00874C10 /* Instance.swift */; };
|
|
||||||
D6C68832272CD40600874C10 /* Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C68831272CD40600874C10 /* Feed.swift */; };
|
|
||||||
D6C68834272CD44900874C10 /* Fervor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C68833272CD44900874C10 /* Fervor.swift */; };
|
|
||||||
D6C68856272CD7C600874C10 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C68855272CD7C600874C10 /* Item.swift */; };
|
|
||||||
D6C68858272CD8CD00874C10 /* Group.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C68857272CD8CD00874C10 /* Group.swift */; };
|
|
||||||
D6E2434C278B456A0005E546 /* ItemsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2434B278B456A0005E546 /* ItemsViewController.swift */; };
|
D6E2434C278B456A0005E546 /* ItemsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2434B278B456A0005E546 /* ItemsViewController.swift */; };
|
||||||
D6E24352278B6DF90005E546 /* ItemsSyncUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E24351278B6DF90005E546 /* ItemsSyncUpdate.swift */; };
|
|
||||||
D6E24357278B96E40005E546 /* Feed+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E24355278B96E40005E546 /* Feed+CoreDataClass.swift */; };
|
|
||||||
D6E24358278B96E40005E546 /* Feed+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E24356278B96E40005E546 /* Feed+CoreDataProperties.swift */; };
|
|
||||||
D6E2435D278B97240005E546 /* Item+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E24359278B97240005E546 /* Item+CoreDataClass.swift */; };
|
|
||||||
D6E2435E278B97240005E546 /* Item+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2435A278B97240005E546 /* Item+CoreDataProperties.swift */; };
|
|
||||||
D6E2435F278B97240005E546 /* Group+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2435B278B97240005E546 /* Group+CoreDataClass.swift */; };
|
|
||||||
D6E24360278B97240005E546 /* Group+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2435C278B97240005E546 /* Group+CoreDataProperties.swift */; };
|
|
||||||
D6E24363278BA1410005E546 /* ItemCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E24361278BA1410005E546 /* ItemCollectionViewCell.swift */; };
|
D6E24363278BA1410005E546 /* ItemCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E24361278BA1410005E546 /* ItemCollectionViewCell.swift */; };
|
||||||
D6E24369278BABB40005E546 /* UIColor+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E24368278BABB40005E546 /* UIColor+App.swift */; };
|
D6E24369278BABB40005E546 /* UIColor+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E24368278BABB40005E546 /* UIColor+App.swift */; };
|
||||||
D6E2436B278BB1880005E546 /* HomeCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2436A278BB1880005E546 /* HomeCollectionViewCell.swift */; };
|
D6E2436B278BB1880005E546 /* HomeCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2436A278BB1880005E546 /* HomeCollectionViewCell.swift */; };
|
||||||
|
@ -58,6 +39,18 @@
|
||||||
D6E24373278BE2B80005E546 /* read.css in Resources */ = {isa = PBXBuildFile; fileRef = D6E24372278BE2B80005E546 /* read.css */; };
|
D6E24373278BE2B80005E546 /* read.css in Resources */ = {isa = PBXBuildFile; fileRef = D6E24372278BE2B80005E546 /* read.css */; };
|
||||||
D6EB531D278C89C300AD2E61 /* AppNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EB531C278C89C300AD2E61 /* AppNavigationController.swift */; };
|
D6EB531D278C89C300AD2E61 /* AppNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EB531C278C89C300AD2E61 /* AppNavigationController.swift */; };
|
||||||
D6EB531F278E4A7500AD2E61 /* StretchyMenuInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EB531E278E4A7500AD2E61 /* StretchyMenuInteraction.swift */; };
|
D6EB531F278E4A7500AD2E61 /* StretchyMenuInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EB531E278E4A7500AD2E61 /* StretchyMenuInteraction.swift */; };
|
||||||
|
D6EEDE87285FA75D009F854E /* Fervor in Frameworks */ = {isa = PBXBuildFile; productRef = D6EEDE86285FA75D009F854E /* Fervor */; };
|
||||||
|
D6EEDE89285FA75F009F854E /* Persistence in Frameworks */ = {isa = PBXBuildFile; productRef = D6EEDE88285FA75F009F854E /* Persistence */; };
|
||||||
|
D6EEDE8B285FA7FD009F854E /* LocalData+Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EEDE8A285FA7FD009F854E /* LocalData+Migration.swift */; };
|
||||||
|
D6EEDE92285FA915009F854E /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6EEDE91285FA915009F854E /* WidgetKit.framework */; };
|
||||||
|
D6EEDE94285FA915009F854E /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6EEDE93285FA915009F854E /* SwiftUI.framework */; };
|
||||||
|
D6EEDE97285FA915009F854E /* Widgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EEDE96285FA915009F854E /* Widgets.swift */; };
|
||||||
|
D6EEDE9A285FA915009F854E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6EEDE99285FA915009F854E /* Assets.xcassets */; };
|
||||||
|
D6EEDE9C285FA915009F854E /* Widgets.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = D6EEDE98285FA915009F854E /* Widgets.intentdefinition */; };
|
||||||
|
D6EEDE9D285FA915009F854E /* Widgets.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = D6EEDE98285FA915009F854E /* Widgets.intentdefinition */; };
|
||||||
|
D6EEDEA0285FA915009F854E /* WidgetsExtension.appex in Embed PlugIns */ = {isa = PBXBuildFile; fileRef = D6EEDE90285FA915009F854E /* WidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
D6EEDEA7285FAE4D009F854E /* Recents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EEDEA5285FAD24009F854E /* Recents.swift */; };
|
||||||
|
D6EEDEA9285FAE60009F854E /* Persistence in Frameworks */ = {isa = PBXBuildFile; productRef = D6EEDEA8285FAE60009F854E /* Persistence */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
@ -82,12 +75,12 @@
|
||||||
remoteGlobalIDString = D6C687E7272CD27600874C10;
|
remoteGlobalIDString = D6C687E7272CD27600874C10;
|
||||||
remoteInfo = Reader;
|
remoteInfo = Reader;
|
||||||
};
|
};
|
||||||
D6C68827272CD2BA00874C10 /* PBXContainerItemProxy */ = {
|
D6EEDE9E285FA915009F854E /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = D6C687E0272CD27600874C10 /* Project object */;
|
containerPortal = D6C687E0272CD27600874C10 /* Project object */;
|
||||||
proxyType = 1;
|
proxyType = 1;
|
||||||
remoteGlobalIDString = D6C68822272CD2BA00874C10;
|
remoteGlobalIDString = D6EEDE8F285FA915009F854E;
|
||||||
remoteInfo = Fervor;
|
remoteInfo = WidgetsExtension;
|
||||||
};
|
};
|
||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
@ -98,6 +91,7 @@
|
||||||
dstPath = "";
|
dstPath = "";
|
||||||
dstSubfolderSpec = 13;
|
dstSubfolderSpec = 13;
|
||||||
files = (
|
files = (
|
||||||
|
D6EEDEA0285FA915009F854E /* WidgetsExtension.appex in Embed PlugIns */,
|
||||||
D6840914279487DC00E327D2 /* ReaderMac.bundle in Embed PlugIns */,
|
D6840914279487DC00E327D2 /* ReaderMac.bundle in Embed PlugIns */,
|
||||||
);
|
);
|
||||||
name = "Embed PlugIns";
|
name = "Embed PlugIns";
|
||||||
|
@ -109,7 +103,6 @@
|
||||||
dstPath = "";
|
dstPath = "";
|
||||||
dstSubfolderSpec = 10;
|
dstSubfolderSpec = 10;
|
||||||
files = (
|
files = (
|
||||||
D6C6882A272CD2BA00874C10 /* Fervor.framework in Embed Frameworks */,
|
|
||||||
);
|
);
|
||||||
name = "Embed Frameworks";
|
name = "Embed Frameworks";
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -119,12 +112,7 @@
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
D608238C27DE729E00D7D5F9 /* ItemListType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListType.swift; sourceTree = "<group>"; };
|
D608238C27DE729E00D7D5F9 /* ItemListType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListType.swift; sourceTree = "<group>"; };
|
||||||
D65B18B12750469D004A9448 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = "<group>"; };
|
D65B18B12750469D004A9448 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = "<group>"; };
|
||||||
D65B18B3275048D9004A9448 /* ClientRegistration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientRegistration.swift; sourceTree = "<group>"; };
|
|
||||||
D65B18B527504920004A9448 /* FervorController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FervorController.swift; sourceTree = "<group>"; };
|
D65B18B527504920004A9448 /* FervorController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FervorController.swift; sourceTree = "<group>"; };
|
||||||
D65B18B72750495D004A9448 /* FervorClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FervorClient.swift; sourceTree = "<group>"; };
|
|
||||||
D65B18B927504A21004A9448 /* URLRequest+Body.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLRequest+Body.swift"; sourceTree = "<group>"; };
|
|
||||||
D65B18BB27504FE7004A9448 /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = "<group>"; };
|
|
||||||
D65B18BD275051A1004A9448 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; };
|
|
||||||
D65B18C027505348004A9448 /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = "<group>"; };
|
D65B18C027505348004A9448 /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = "<group>"; };
|
||||||
D68408E727947D0800E327D2 /* PrefsSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsSceneDelegate.swift; sourceTree = "<group>"; };
|
D68408E727947D0800E327D2 /* PrefsSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsSceneDelegate.swift; sourceTree = "<group>"; };
|
||||||
D68408EC2794803D00E327D2 /* PrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsView.swift; sourceTree = "<group>"; };
|
D68408EC2794803D00E327D2 /* PrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -138,11 +126,11 @@
|
||||||
D68B303E27923C0000E8B3FA /* Reader.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Reader.entitlements; sourceTree = "<group>"; };
|
D68B303E27923C0000E8B3FA /* Reader.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Reader.entitlements; sourceTree = "<group>"; };
|
||||||
D68B303F2792729A00E8B3FA /* AppSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSplitViewController.swift; sourceTree = "<group>"; };
|
D68B303F2792729A00E8B3FA /* AppSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSplitViewController.swift; sourceTree = "<group>"; };
|
||||||
D68B304127932ED500E8B3FA /* UserActivities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivities.swift; sourceTree = "<group>"; };
|
D68B304127932ED500E8B3FA /* UserActivities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivities.swift; sourceTree = "<group>"; };
|
||||||
D6A8A33327766C2800CCEC72 /* PersistentContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistentContainer.swift; sourceTree = "<group>"; };
|
D6AB5E9A285F6FE100157F2F /* Fervor */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Fervor; sourceTree = "<group>"; };
|
||||||
|
D6AB5E9B285F706F00157F2F /* Persistence */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Persistence; sourceTree = "<group>"; };
|
||||||
D6C687E8272CD27600874C10 /* Reader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Reader.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
D6C687E8272CD27600874C10 /* Reader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Reader.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D6C687EB272CD27600874C10 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
D6C687EB272CD27600874C10 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
D6C687ED272CD27600874C10 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
D6C687ED272CD27600874C10 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||||
D6C687F5272CD27600874C10 /* Reader.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Reader.xcdatamodel; sourceTree = "<group>"; };
|
|
||||||
D6C687F7272CD27700874C10 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
D6C687F7272CD27700874C10 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
D6C687FA272CD27700874C10 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
D6C687FA272CD27700874C10 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
D6C687FC272CD27700874C10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
D6C687FC272CD27700874C10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
@ -151,21 +139,9 @@
|
||||||
D6C6880B272CD27700874C10 /* ReaderUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReaderUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
D6C6880B272CD27700874C10 /* ReaderUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReaderUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D6C6880F272CD27700874C10 /* ReaderUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderUITests.swift; sourceTree = "<group>"; };
|
D6C6880F272CD27700874C10 /* ReaderUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderUITests.swift; sourceTree = "<group>"; };
|
||||||
D6C68811272CD27700874C10 /* ReaderUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderUITestsLaunchTests.swift; sourceTree = "<group>"; };
|
D6C68811272CD27700874C10 /* ReaderUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderUITestsLaunchTests.swift; sourceTree = "<group>"; };
|
||||||
D6C68823272CD2BA00874C10 /* Fervor.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Fervor.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
D6D5FA23285FE9DB00BBF188 /* WidgetHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetHelper.swift; sourceTree = "<group>"; };
|
||||||
D6C68825272CD2BA00874C10 /* Fervor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Fervor.h; sourceTree = "<group>"; };
|
D6D5FA26285FEA6900BBF188 /* WidgetData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetData.swift; sourceTree = "<group>"; };
|
||||||
D6C6882F272CD2CF00874C10 /* Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instance.swift; sourceTree = "<group>"; };
|
|
||||||
D6C68831272CD40600874C10 /* Feed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Feed.swift; sourceTree = "<group>"; };
|
|
||||||
D6C68833272CD44900874C10 /* Fervor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fervor.swift; sourceTree = "<group>"; };
|
|
||||||
D6C68855272CD7C600874C10 /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = "<group>"; };
|
|
||||||
D6C68857272CD8CD00874C10 /* Group.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Group.swift; sourceTree = "<group>"; };
|
|
||||||
D6E2434B278B456A0005E546 /* ItemsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsViewController.swift; sourceTree = "<group>"; };
|
D6E2434B278B456A0005E546 /* ItemsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsViewController.swift; sourceTree = "<group>"; };
|
||||||
D6E24351278B6DF90005E546 /* ItemsSyncUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsSyncUpdate.swift; sourceTree = "<group>"; };
|
|
||||||
D6E24355278B96E40005E546 /* Feed+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Feed+CoreDataClass.swift"; sourceTree = "<group>"; };
|
|
||||||
D6E24356278B96E40005E546 /* Feed+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Feed+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
|
||||||
D6E24359278B97240005E546 /* Item+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Item+CoreDataClass.swift"; sourceTree = "<group>"; };
|
|
||||||
D6E2435A278B97240005E546 /* Item+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Item+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
|
||||||
D6E2435B278B97240005E546 /* Group+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Group+CoreDataClass.swift"; sourceTree = "<group>"; };
|
|
||||||
D6E2435C278B97240005E546 /* Group+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Group+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
|
||||||
D6E24361278BA1410005E546 /* ItemCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemCollectionViewCell.swift; sourceTree = "<group>"; };
|
D6E24361278BA1410005E546 /* ItemCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
D6E24368278BABB40005E546 /* UIColor+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+App.swift"; sourceTree = "<group>"; };
|
D6E24368278BABB40005E546 /* UIColor+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+App.swift"; sourceTree = "<group>"; };
|
||||||
D6E2436A278BB1880005E546 /* HomeCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCollectionViewCell.swift; sourceTree = "<group>"; };
|
D6E2436A278BB1880005E546 /* HomeCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
@ -173,6 +149,16 @@
|
||||||
D6E24372278BE2B80005E546 /* read.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = read.css; sourceTree = "<group>"; };
|
D6E24372278BE2B80005E546 /* read.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = read.css; sourceTree = "<group>"; };
|
||||||
D6EB531C278C89C300AD2E61 /* AppNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNavigationController.swift; sourceTree = "<group>"; };
|
D6EB531C278C89C300AD2E61 /* AppNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNavigationController.swift; sourceTree = "<group>"; };
|
||||||
D6EB531E278E4A7500AD2E61 /* StretchyMenuInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StretchyMenuInteraction.swift; sourceTree = "<group>"; };
|
D6EB531E278E4A7500AD2E61 /* StretchyMenuInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StretchyMenuInteraction.swift; sourceTree = "<group>"; };
|
||||||
|
D6EEDE8A285FA7FD009F854E /* LocalData+Migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocalData+Migration.swift"; sourceTree = "<group>"; };
|
||||||
|
D6EEDE90285FA915009F854E /* WidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
D6EEDE91285FA915009F854E /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
|
||||||
|
D6EEDE93285FA915009F854E /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
||||||
|
D6EEDE96285FA915009F854E /* Widgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Widgets.swift; sourceTree = "<group>"; };
|
||||||
|
D6EEDE98285FA915009F854E /* Widgets.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = Widgets.intentdefinition; sourceTree = "<group>"; };
|
||||||
|
D6EEDE99285FA915009F854E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
D6EEDE9B285FA915009F854E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
D6EEDEA1285FA915009F854E /* WidgetsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetsExtension.entitlements; sourceTree = "<group>"; };
|
||||||
|
D6EEDEA5285FAD24009F854E /* Recents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Recents.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
@ -187,7 +173,8 @@
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D6C68829272CD2BA00874C10 /* Fervor.framework in Frameworks */,
|
D6EEDE89285FA75F009F854E /* Persistence in Frameworks */,
|
||||||
|
D6EEDE87285FA75D009F854E /* Fervor in Frameworks */,
|
||||||
D6E24371278BE1250005E546 /* HTMLEntities in Frameworks */,
|
D6E24371278BE1250005E546 /* HTMLEntities in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -206,10 +193,13 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
D6C68820272CD2BA00874C10 /* Frameworks */ = {
|
D6EEDE8D285FA915009F854E /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
D6EEDEA9285FAE60009F854E /* Persistence in Frameworks */,
|
||||||
|
D6EEDE94285FA915009F854E /* SwiftUI.framework in Frameworks */,
|
||||||
|
D6EEDE92285FA915009F854E /* WidgetKit.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -267,33 +257,23 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D68B3037279099FD00E8B3FA /* liblolhtml.a */,
|
D68B3037279099FD00E8B3FA /* liblolhtml.a */,
|
||||||
|
D6EEDE91285FA915009F854E /* WidgetKit.framework */,
|
||||||
|
D6EEDE93285FA915009F854E /* SwiftUI.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
D6A8A33527766E9300CCEC72 /* CoreData */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D6C687F4272CD27600874C10 /* Reader.xcdatamodeld */,
|
|
||||||
D6A8A33327766C2800CCEC72 /* PersistentContainer.swift */,
|
|
||||||
D6E24355278B96E40005E546 /* Feed+CoreDataClass.swift */,
|
|
||||||
D6E24356278B96E40005E546 /* Feed+CoreDataProperties.swift */,
|
|
||||||
D6E2435B278B97240005E546 /* Group+CoreDataClass.swift */,
|
|
||||||
D6E2435C278B97240005E546 /* Group+CoreDataProperties.swift */,
|
|
||||||
D6E24359278B97240005E546 /* Item+CoreDataClass.swift */,
|
|
||||||
D6E2435A278B97240005E546 /* Item+CoreDataProperties.swift */,
|
|
||||||
);
|
|
||||||
path = CoreData;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D6C687DF272CD27600874C10 = {
|
D6C687DF272CD27600874C10 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D6EEDEA1285FA915009F854E /* WidgetsExtension.entitlements */,
|
||||||
|
D6AB5E9B285F706F00157F2F /* Persistence */,
|
||||||
|
D6AB5E9A285F6FE100157F2F /* Fervor */,
|
||||||
D6C687EA272CD27600874C10 /* Reader */,
|
D6C687EA272CD27600874C10 /* Reader */,
|
||||||
D6C68804272CD27700874C10 /* ReaderTests */,
|
D6C68804272CD27700874C10 /* ReaderTests */,
|
||||||
D6C6880E272CD27700874C10 /* ReaderUITests */,
|
D6C6880E272CD27700874C10 /* ReaderUITests */,
|
||||||
D6840911279486C400E327D2 /* ReaderMac */,
|
D6840911279486C400E327D2 /* ReaderMac */,
|
||||||
D6C68824272CD2BA00874C10 /* Fervor */,
|
D6EEDE95285FA915009F854E /* Widgets */,
|
||||||
D6C687E9272CD27600874C10 /* Products */,
|
D6C687E9272CD27600874C10 /* Products */,
|
||||||
D68B302E278FDCE200E8B3FA /* Frameworks */,
|
D68B302E278FDCE200E8B3FA /* Frameworks */,
|
||||||
);
|
);
|
||||||
|
@ -305,8 +285,8 @@
|
||||||
D6C687E8272CD27600874C10 /* Reader.app */,
|
D6C687E8272CD27600874C10 /* Reader.app */,
|
||||||
D6C68801272CD27700874C10 /* ReaderTests.xctest */,
|
D6C68801272CD27700874C10 /* ReaderTests.xctest */,
|
||||||
D6C6880B272CD27700874C10 /* ReaderUITests.xctest */,
|
D6C6880B272CD27700874C10 /* ReaderUITests.xctest */,
|
||||||
D6C68823272CD2BA00874C10 /* Fervor.framework */,
|
|
||||||
D684090D279486BF00E327D2 /* ReaderMac.bundle */,
|
D684090D279486BF00E327D2 /* ReaderMac.bundle */,
|
||||||
|
D6EEDE90285FA915009F854E /* WidgetsExtension.appex */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -320,14 +300,14 @@
|
||||||
D6C687ED272CD27600874C10 /* SceneDelegate.swift */,
|
D6C687ED272CD27600874C10 /* SceneDelegate.swift */,
|
||||||
D68408E727947D0800E327D2 /* PrefsSceneDelegate.swift */,
|
D68408E727947D0800E327D2 /* PrefsSceneDelegate.swift */,
|
||||||
D65B18B527504920004A9448 /* FervorController.swift */,
|
D65B18B527504920004A9448 /* FervorController.swift */,
|
||||||
D65B18BD275051A1004A9448 /* LocalData.swift */,
|
|
||||||
D6E24368278BABB40005E546 /* UIColor+App.swift */,
|
D6E24368278BABB40005E546 /* UIColor+App.swift */,
|
||||||
D6EB531E278E4A7500AD2E61 /* StretchyMenuInteraction.swift */,
|
D6EB531E278E4A7500AD2E61 /* StretchyMenuInteraction.swift */,
|
||||||
D68B303527907D9200E8B3FA /* ExcerptGenerator.swift */,
|
D68B303527907D9200E8B3FA /* ExcerptGenerator.swift */,
|
||||||
D68B304127932ED500E8B3FA /* UserActivities.swift */,
|
D68B304127932ED500E8B3FA /* UserActivities.swift */,
|
||||||
D68408EE2794808E00E327D2 /* Preferences.swift */,
|
D68408EE2794808E00E327D2 /* Preferences.swift */,
|
||||||
D608238C27DE729E00D7D5F9 /* ItemListType.swift */,
|
D608238C27DE729E00D7D5F9 /* ItemListType.swift */,
|
||||||
D6A8A33527766E9300CCEC72 /* CoreData */,
|
D6EEDE8A285FA7FD009F854E /* LocalData+Migration.swift */,
|
||||||
|
D6D5FA25285FEA5B00BBF188 /* Widgets */,
|
||||||
D65B18AF2750468B004A9448 /* Screens */,
|
D65B18AF2750468B004A9448 /* Screens */,
|
||||||
D6C687F7272CD27700874C10 /* Assets.xcassets */,
|
D6C687F7272CD27700874C10 /* Assets.xcassets */,
|
||||||
D6C687F9272CD27700874C10 /* LaunchScreen.storyboard */,
|
D6C687F9272CD27700874C10 /* LaunchScreen.storyboard */,
|
||||||
|
@ -355,22 +335,13 @@
|
||||||
path = ReaderUITests;
|
path = ReaderUITests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
D6C68824272CD2BA00874C10 /* Fervor */ = {
|
D6D5FA25285FEA5B00BBF188 /* Widgets */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D6C68825272CD2BA00874C10 /* Fervor.h */,
|
D6D5FA23285FE9DB00BBF188 /* WidgetHelper.swift */,
|
||||||
D6C68833272CD44900874C10 /* Fervor.swift */,
|
D6D5FA26285FEA6900BBF188 /* WidgetData.swift */,
|
||||||
D65B18B72750495D004A9448 /* FervorClient.swift */,
|
|
||||||
D65B18B927504A21004A9448 /* URLRequest+Body.swift */,
|
|
||||||
D65B18B3275048D9004A9448 /* ClientRegistration.swift */,
|
|
||||||
D6C68831272CD40600874C10 /* Feed.swift */,
|
|
||||||
D6C68857272CD8CD00874C10 /* Group.swift */,
|
|
||||||
D6C6882F272CD2CF00874C10 /* Instance.swift */,
|
|
||||||
D6C68855272CD7C600874C10 /* Item.swift */,
|
|
||||||
D6E24351278B6DF90005E546 /* ItemsSyncUpdate.swift */,
|
|
||||||
D65B18BB27504FE7004A9448 /* Token.swift */,
|
|
||||||
);
|
);
|
||||||
path = Fervor;
|
path = Widgets;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
D6E2434A278B455C0005E546 /* Items */ = {
|
D6E2434A278B455C0005E546 /* Items */ = {
|
||||||
|
@ -390,18 +361,19 @@
|
||||||
path = Read;
|
path = Read;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
/* End PBXGroup section */
|
D6EEDE95285FA915009F854E /* Widgets */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
/* Begin PBXHeadersBuildPhase section */
|
children = (
|
||||||
D6C6881E272CD2BA00874C10 /* Headers */ = {
|
D6EEDE96285FA915009F854E /* Widgets.swift */,
|
||||||
isa = PBXHeadersBuildPhase;
|
D6EEDEA5285FAD24009F854E /* Recents.swift */,
|
||||||
buildActionMask = 2147483647;
|
D6EEDE98285FA915009F854E /* Widgets.intentdefinition */,
|
||||||
files = (
|
D6EEDE99285FA915009F854E /* Assets.xcassets */,
|
||||||
D6C68826272CD2BA00874C10 /* Fervor.h in Headers */,
|
D6EEDE9B285FA915009F854E /* Info.plist */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
path = Widgets;
|
||||||
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
/* End PBXHeadersBuildPhase section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
D684090C279486BF00E327D2 /* ReaderMac */ = {
|
D684090C279486BF00E327D2 /* ReaderMac */ = {
|
||||||
|
@ -435,12 +407,14 @@
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
D6C68828272CD2BA00874C10 /* PBXTargetDependency */,
|
|
||||||
D6840916279487DC00E327D2 /* PBXTargetDependency */,
|
D6840916279487DC00E327D2 /* PBXTargetDependency */,
|
||||||
|
D6EEDE9F285FA915009F854E /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = Reader;
|
name = Reader;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
D6E24370278BE1250005E546 /* HTMLEntities */,
|
D6E24370278BE1250005E546 /* HTMLEntities */,
|
||||||
|
D6EEDE86285FA75D009F854E /* Fervor */,
|
||||||
|
D6EEDE88285FA75F009F854E /* Persistence */,
|
||||||
);
|
);
|
||||||
productName = Reader;
|
productName = Reader;
|
||||||
productReference = D6C687E8272CD27600874C10 /* Reader.app */;
|
productReference = D6C687E8272CD27600874C10 /* Reader.app */;
|
||||||
|
@ -482,23 +456,25 @@
|
||||||
productReference = D6C6880B272CD27700874C10 /* ReaderUITests.xctest */;
|
productReference = D6C6880B272CD27700874C10 /* ReaderUITests.xctest */;
|
||||||
productType = "com.apple.product-type.bundle.ui-testing";
|
productType = "com.apple.product-type.bundle.ui-testing";
|
||||||
};
|
};
|
||||||
D6C68822272CD2BA00874C10 /* Fervor */ = {
|
D6EEDE8F285FA915009F854E /* WidgetsExtension */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = D6C6882B272CD2BA00874C10 /* Build configuration list for PBXNativeTarget "Fervor" */;
|
buildConfigurationList = D6EEDEA2285FA915009F854E /* Build configuration list for PBXNativeTarget "WidgetsExtension" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
D6C6881E272CD2BA00874C10 /* Headers */,
|
D6EEDE8C285FA915009F854E /* Sources */,
|
||||||
D6C6881F272CD2BA00874C10 /* Sources */,
|
D6EEDE8D285FA915009F854E /* Frameworks */,
|
||||||
D6C68820272CD2BA00874C10 /* Frameworks */,
|
D6EEDE8E285FA915009F854E /* Resources */,
|
||||||
D6C68821272CD2BA00874C10 /* Resources */,
|
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
);
|
);
|
||||||
name = Fervor;
|
name = WidgetsExtension;
|
||||||
productName = Fervor;
|
packageProductDependencies = (
|
||||||
productReference = D6C68823272CD2BA00874C10 /* Fervor.framework */;
|
D6EEDEA8285FAE60009F854E /* Persistence */,
|
||||||
productType = "com.apple.product-type.framework";
|
);
|
||||||
|
productName = WidgetsExtension;
|
||||||
|
productReference = D6EEDE90285FA915009F854E /* WidgetsExtension.appex */;
|
||||||
|
productType = "com.apple.product-type.app-extension";
|
||||||
};
|
};
|
||||||
/* End PBXNativeTarget section */
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
@ -507,7 +483,7 @@
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = 1;
|
BuildIndependentTargetsInParallel = 1;
|
||||||
LastSwiftUpdateCheck = 1320;
|
LastSwiftUpdateCheck = 1400;
|
||||||
LastUpgradeCheck = 1400;
|
LastUpgradeCheck = 1400;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
D684090C279486BF00E327D2 = {
|
D684090C279486BF00E327D2 = {
|
||||||
|
@ -525,9 +501,8 @@
|
||||||
CreatedOnToolsVersion = 13.2;
|
CreatedOnToolsVersion = 13.2;
|
||||||
TestTargetID = D6C687E7272CD27600874C10;
|
TestTargetID = D6C687E7272CD27600874C10;
|
||||||
};
|
};
|
||||||
D6C68822272CD2BA00874C10 = {
|
D6EEDE8F285FA915009F854E = {
|
||||||
CreatedOnToolsVersion = 13.2;
|
CreatedOnToolsVersion = 14.0;
|
||||||
LastSwiftMigration = 1320;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -550,8 +525,8 @@
|
||||||
D6C687E7272CD27600874C10 /* Reader */,
|
D6C687E7272CD27600874C10 /* Reader */,
|
||||||
D6C68800272CD27700874C10 /* ReaderTests */,
|
D6C68800272CD27700874C10 /* ReaderTests */,
|
||||||
D6C6880A272CD27700874C10 /* ReaderUITests */,
|
D6C6880A272CD27700874C10 /* ReaderUITests */,
|
||||||
D6C68822272CD2BA00874C10 /* Fervor */,
|
|
||||||
D684090C279486BF00E327D2 /* ReaderMac */,
|
D684090C279486BF00E327D2 /* ReaderMac */,
|
||||||
|
D6EEDE8F285FA915009F854E /* WidgetsExtension */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
@ -589,10 +564,11 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
D6C68821272CD2BA00874C10 /* Resources */ = {
|
D6EEDE8E285FA915009F854E /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
D6EEDE9A285FA915009F854E /* Assets.xcassets in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -633,30 +609,25 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D6A8A33427766C2800CCEC72 /* PersistentContainer.swift in Sources */,
|
|
||||||
D6E24357278B96E40005E546 /* Feed+CoreDataClass.swift in Sources */,
|
|
||||||
D608238D27DE729E00D7D5F9 /* ItemListType.swift in Sources */,
|
D608238D27DE729E00D7D5F9 /* ItemListType.swift in Sources */,
|
||||||
D65B18B627504920004A9448 /* FervorController.swift in Sources */,
|
D65B18B627504920004A9448 /* FervorController.swift in Sources */,
|
||||||
|
D6D5FA27285FEA6900BBF188 /* WidgetData.swift in Sources */,
|
||||||
D68B304227932ED500E8B3FA /* UserActivities.swift in Sources */,
|
D68B304227932ED500E8B3FA /* UserActivities.swift in Sources */,
|
||||||
D6C687EC272CD27600874C10 /* AppDelegate.swift in Sources */,
|
D6C687EC272CD27600874C10 /* AppDelegate.swift in Sources */,
|
||||||
D6C687F6272CD27600874C10 /* Reader.xcdatamodeld in Sources */,
|
|
||||||
D6E2436B278BB1880005E546 /* HomeCollectionViewCell.swift in Sources */,
|
D6E2436B278BB1880005E546 /* HomeCollectionViewCell.swift in Sources */,
|
||||||
D6E2435F278B97240005E546 /* Group+CoreDataClass.swift in Sources */,
|
|
||||||
D6E24369278BABB40005E546 /* UIColor+App.swift in Sources */,
|
D6E24369278BABB40005E546 /* UIColor+App.swift in Sources */,
|
||||||
D6E2435D278B97240005E546 /* Item+CoreDataClass.swift in Sources */,
|
D6EEDE8B285FA7FD009F854E /* LocalData+Migration.swift in Sources */,
|
||||||
D68408E827947D0800E327D2 /* PrefsSceneDelegate.swift in Sources */,
|
D68408E827947D0800E327D2 /* PrefsSceneDelegate.swift in Sources */,
|
||||||
D6EB531D278C89C300AD2E61 /* AppNavigationController.swift in Sources */,
|
D6EB531D278C89C300AD2E61 /* AppNavigationController.swift in Sources */,
|
||||||
D6E24360278B97240005E546 /* Group+CoreDataProperties.swift in Sources */,
|
|
||||||
D6E2434C278B456A0005E546 /* ItemsViewController.swift in Sources */,
|
D6E2434C278B456A0005E546 /* ItemsViewController.swift in Sources */,
|
||||||
D6E2435E278B97240005E546 /* Item+CoreDataProperties.swift in Sources */,
|
|
||||||
D6EB531F278E4A7500AD2E61 /* StretchyMenuInteraction.swift in Sources */,
|
D6EB531F278E4A7500AD2E61 /* StretchyMenuInteraction.swift in Sources */,
|
||||||
D6E24358278B96E40005E546 /* Feed+CoreDataProperties.swift in Sources */,
|
|
||||||
D68B30402792729A00E8B3FA /* AppSplitViewController.swift in Sources */,
|
D68B30402792729A00E8B3FA /* AppSplitViewController.swift in Sources */,
|
||||||
D65B18BE275051A1004A9448 /* LocalData.swift in Sources */,
|
|
||||||
D65B18B22750469D004A9448 /* LoginViewController.swift in Sources */,
|
D65B18B22750469D004A9448 /* LoginViewController.swift in Sources */,
|
||||||
|
D6EEDE9D285FA915009F854E /* Widgets.intentdefinition in Sources */,
|
||||||
D68408ED2794803D00E327D2 /* PrefsView.swift in Sources */,
|
D68408ED2794803D00E327D2 /* PrefsView.swift in Sources */,
|
||||||
D68408EF2794808E00E327D2 /* Preferences.swift in Sources */,
|
D68408EF2794808E00E327D2 /* Preferences.swift in Sources */,
|
||||||
D68B303627907D9200E8B3FA /* ExcerptGenerator.swift in Sources */,
|
D68B303627907D9200E8B3FA /* ExcerptGenerator.swift in Sources */,
|
||||||
|
D6D5FA24285FE9DB00BBF188 /* WidgetHelper.swift in Sources */,
|
||||||
D6E24363278BA1410005E546 /* ItemCollectionViewCell.swift in Sources */,
|
D6E24363278BA1410005E546 /* ItemCollectionViewCell.swift in Sources */,
|
||||||
D6E2436E278BD8160005E546 /* ReadViewController.swift in Sources */,
|
D6E2436E278BD8160005E546 /* ReadViewController.swift in Sources */,
|
||||||
D65B18C127505348004A9448 /* HomeViewController.swift in Sources */,
|
D65B18C127505348004A9448 /* HomeViewController.swift in Sources */,
|
||||||
|
@ -681,20 +652,14 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
D6C6881F272CD2BA00874C10 /* Sources */ = {
|
D6EEDE8C285FA915009F854E /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D65B18BC27504FE7004A9448 /* Token.swift in Sources */,
|
D6EEDE9C285FA915009F854E /* Widgets.intentdefinition in Sources */,
|
||||||
D6C68856272CD7C600874C10 /* Item.swift in Sources */,
|
D6D5FA28285FEA8A00BBF188 /* WidgetData.swift in Sources */,
|
||||||
D6C68830272CD2CF00874C10 /* Instance.swift in Sources */,
|
D6EEDEA7285FAE4D009F854E /* Recents.swift in Sources */,
|
||||||
D6E24352278B6DF90005E546 /* ItemsSyncUpdate.swift in Sources */,
|
D6EEDE97285FA915009F854E /* Widgets.swift in Sources */,
|
||||||
D6C68832272CD40600874C10 /* Feed.swift in Sources */,
|
|
||||||
D6C68858272CD8CD00874C10 /* Group.swift in Sources */,
|
|
||||||
D6C68834272CD44900874C10 /* Fervor.swift in Sources */,
|
|
||||||
D65B18BA27504A21004A9448 /* URLRequest+Body.swift in Sources */,
|
|
||||||
D65B18B82750495D004A9448 /* FervorClient.swift in Sources */,
|
|
||||||
D65B18B4275048D9004A9448 /* ClientRegistration.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -717,10 +682,10 @@
|
||||||
target = D6C687E7272CD27600874C10 /* Reader */;
|
target = D6C687E7272CD27600874C10 /* Reader */;
|
||||||
targetProxy = D6C6880C272CD27700874C10 /* PBXContainerItemProxy */;
|
targetProxy = D6C6880C272CD27700874C10 /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
D6C68828272CD2BA00874C10 /* PBXTargetDependency */ = {
|
D6EEDE9F285FA915009F854E /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = D6C68822272CD2BA00874C10 /* Fervor */;
|
target = D6EEDE8F285FA915009F854E /* WidgetsExtension */;
|
||||||
targetProxy = D6C68827272CD2BA00874C10 /* PBXContainerItemProxy */;
|
targetProxy = D6EEDE9E285FA915009F854E /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
/* End PBXTargetDependency section */
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
@ -1077,68 +1042,63 @@
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
D6C6882C272CD2BA00874C10 /* Debug */ = {
|
D6EEDEA3285FA915009F854E /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_MODULES = YES;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = WidgetsExtension.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEFINES_MODULE = YES;
|
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||||
DEVELOPMENT_TEAM = ZPBBSK8L8B;
|
|
||||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
|
||||||
DYLIB_CURRENT_VERSION = 1;
|
|
||||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = Widgets/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = Widgets;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@loader_path/Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.Fervor;
|
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.Reader.Widgets;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
SUPPORTS_MACCATALYST = YES;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
|
||||||
VERSION_INFO_PREFIX = "";
|
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
D6C6882D272CD2BA00874C10 /* Release */ = {
|
D6EEDEA4285FA915009F854E /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_MODULES = YES;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = WidgetsExtension.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEFINES_MODULE = YES;
|
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||||
DEVELOPMENT_TEAM = ZPBBSK8L8B;
|
|
||||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
|
||||||
DYLIB_CURRENT_VERSION = 1;
|
|
||||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = Widgets/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = Widgets;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@loader_path/Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.Fervor;
|
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.Reader.Widgets;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
SUPPORTS_MACCATALYST = YES;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
|
||||||
VERSION_INFO_PREFIX = "";
|
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
@ -1190,11 +1150,11 @@
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
D6C6882B272CD2BA00874C10 /* Build configuration list for PBXNativeTarget "Fervor" */ = {
|
D6EEDEA2285FA915009F854E /* Build configuration list for PBXNativeTarget "WidgetsExtension" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
D6C6882C272CD2BA00874C10 /* Debug */,
|
D6EEDEA3285FA915009F854E /* Debug */,
|
||||||
D6C6882D272CD2BA00874C10 /* Release */,
|
D6EEDEA4285FA915009F854E /* Release */,
|
||||||
);
|
);
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
|
@ -1218,20 +1178,19 @@
|
||||||
package = D6E2436F278BE1250005E546 /* XCRemoteSwiftPackageReference "swift-html-entities" */;
|
package = D6E2436F278BE1250005E546 /* XCRemoteSwiftPackageReference "swift-html-entities" */;
|
||||||
productName = HTMLEntities;
|
productName = HTMLEntities;
|
||||||
};
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
D6EEDE86285FA75D009F854E /* Fervor */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
/* Begin XCVersionGroup section */
|
productName = Fervor;
|
||||||
D6C687F4272CD27600874C10 /* Reader.xcdatamodeld */ = {
|
|
||||||
isa = XCVersionGroup;
|
|
||||||
children = (
|
|
||||||
D6C687F5272CD27600874C10 /* Reader.xcdatamodel */,
|
|
||||||
);
|
|
||||||
currentVersion = D6C687F5272CD27600874C10 /* Reader.xcdatamodel */;
|
|
||||||
path = Reader.xcdatamodeld;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
versionGroupType = wrapper.xcdatamodel;
|
|
||||||
};
|
};
|
||||||
/* End XCVersionGroup section */
|
D6EEDE88285FA75F009F854E /* Persistence */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = Persistence;
|
||||||
|
};
|
||||||
|
D6EEDEA8285FAE60009F854E /* Persistence */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = Persistence;
|
||||||
|
};
|
||||||
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
rootObject = D6C687E0272CD27600874C10 /* Project object */;
|
rootObject = D6C687E0272CD27600874C10 /* Project object */;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import UIKit
|
||||||
import WebKit
|
import WebKit
|
||||||
import OSLog
|
import OSLog
|
||||||
import Combine
|
import Combine
|
||||||
|
import Persistence
|
||||||
|
|
||||||
@main
|
@main
|
||||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
@ -20,6 +21,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
|
LocalData.migrateIfNecessary()
|
||||||
|
|
||||||
swizzleWKWebView()
|
swizzleWKWebView()
|
||||||
|
|
||||||
Preferences.shared.objectWillChange
|
Preferences.shared.objectWillChange
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import OSLog
|
import OSLog
|
||||||
import CoreData
|
import CoreData
|
||||||
|
import Persistence
|
||||||
|
|
||||||
// public so that it can be imported in ReaderTests even when Reader is compiled in release mode (w/ testing disabled)
|
// public so that it can be imported in ReaderTests even when Reader is compiled in release mode (w/ testing disabled)
|
||||||
public struct ExcerptGenerator {
|
public struct ExcerptGenerator {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import Foundation
|
||||||
import Fervor
|
import Fervor
|
||||||
import OSLog
|
import OSLog
|
||||||
import Combine
|
import Combine
|
||||||
|
import Persistence
|
||||||
|
|
||||||
actor FervorController {
|
actor FervorController {
|
||||||
|
|
||||||
|
@ -42,8 +43,6 @@ actor FervorController {
|
||||||
} else {
|
} else {
|
||||||
self.persistentContainer = nil
|
self.persistentContainer = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
persistentContainer?.fervorController = self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
convenience init(account: LocalData.Account) async {
|
convenience init(account: LocalData.Account) async {
|
||||||
|
@ -85,11 +84,13 @@ actor FervorController {
|
||||||
let lastSync = try await persistentContainer.lastSyncDate()
|
let lastSync = try await persistentContainer.lastSyncDate()
|
||||||
logger.info("Syncing items with last sync date: \(String(describing: lastSync), privacy: .public)")
|
logger.info("Syncing items with last sync date: \(String(describing: lastSync), privacy: .public)")
|
||||||
let update = try await client.syncItems(lastSync: lastSync)
|
let update = try await client.syncItems(lastSync: lastSync)
|
||||||
try await persistentContainer.syncItems(update, setSyncState: setSyncState(_:))
|
try await persistentContainer.syncItems(update, setProgress: { count, total in self.setSyncState(.updateItems(current: count, total: total)) })
|
||||||
try await persistentContainer.updateLastSyncDate(update.syncTimestamp)
|
try await persistentContainer.updateLastSyncDate(update.syncTimestamp)
|
||||||
|
|
||||||
setSyncState(.excerpts)
|
setSyncState(.excerpts)
|
||||||
await ExcerptGenerator.generateAll(self)
|
await ExcerptGenerator.generateAll(self)
|
||||||
|
|
||||||
|
await WidgetHelper.updateWidgetData(fervorController: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
|
@ -137,7 +138,7 @@ actor FervorController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func markItem(_ item: Item, read: Bool) async {
|
func markItem(_ item: Persistence.Item, read: Bool) async {
|
||||||
item.read = read
|
item.read = read
|
||||||
do {
|
do {
|
||||||
let f = read ? client.read(item:) : client.unread(item:)
|
let f = read ? client.read(item:) : client.unread(item:)
|
||||||
|
@ -156,11 +157,11 @@ actor FervorController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func fetchItem(id: String) async throws -> Item? {
|
func fetchItem(id: String) async throws -> Persistence.Item? {
|
||||||
guard let serverItem = try await client.item(id: id) else {
|
guard let serverItem = try await client.item(id: id) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
let item = Item(context: persistentContainer.viewContext)
|
let item = Persistence.Item(context: persistentContainer.viewContext)
|
||||||
item.updateFromServer(serverItem)
|
item.updateFromServer(serverItem)
|
||||||
try persistentContainer.saveViewContext()
|
try persistentContainer.saveViewContext()
|
||||||
return item
|
return item
|
||||||
|
|
|
@ -4,14 +4,15 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSUserActivityTypes</key>
|
<key>NSUserActivityTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.read-unread</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.activate-account</string>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.add-account</string>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.preferences</string>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.read-all</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.read-all</string>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.read-feed</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.read-feed</string>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.read-group</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.read-group</string>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.read-item</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.read-item</string>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.preferences</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.read-unread</string>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.add-account</string>
|
<string>ConfigurationIntent</string>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.activate-account</string>
|
|
||||||
</array>
|
</array>
|
||||||
<key>UIApplicationSceneManifest</key>
|
<key>UIApplicationSceneManifest</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -28,10 +29,10 @@
|
||||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||||
</dict>
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>UISceneDelegateClassName</key>
|
|
||||||
<string>$(PRODUCT_MODULE_NAME).PrefsSceneDelegate</string>
|
|
||||||
<key>UISceneConfigurationName</key>
|
<key>UISceneConfigurationName</key>
|
||||||
<string>prefs</string>
|
<string>prefs</string>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>$(PRODUCT_MODULE_NAME).PrefsSceneDelegate</string>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreData
|
import CoreData
|
||||||
|
import Persistence
|
||||||
|
|
||||||
enum ItemListType: Hashable {
|
enum ItemListType: Hashable {
|
||||||
case unread
|
case unread
|
||||||
|
@ -43,8 +44,8 @@ enum ItemListType: Hashable {
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
var countFetchRequest: NSFetchRequest<Reader.Item>? {
|
var countFetchRequest: NSFetchRequest<Item>? {
|
||||||
let req = Reader.Item.fetchRequest()
|
let req = Item.fetchRequest()
|
||||||
switch self {
|
switch self {
|
||||||
case .unread:
|
case .unread:
|
||||||
req.predicate = NSPredicate(format: "read = NO")
|
req.predicate = NSPredicate(format: "read = NO")
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
// LocalData+Migration.swift
|
||||||
|
// Reader
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 6/19/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Persistence
|
||||||
|
|
||||||
|
extension LocalData {
|
||||||
|
static func migrateIfNecessary() {
|
||||||
|
if let accounts = UserDefaults.standard.object(forKey: "accounts") {
|
||||||
|
UserDefaults.standard.removeObject(forKey: "accounts")
|
||||||
|
defaults.set(accounts, forKey: "accounts")
|
||||||
|
}
|
||||||
|
if let mostRecentAccountID = UserDefaults.standard.object(forKey: "mostRecentAccountID") {
|
||||||
|
UserDefaults.standard.removeObject(forKey: "mostRecentAccountID")
|
||||||
|
defaults.set(mostRecentAccountID, forKey: "mostRecentAccountID")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,6 +4,10 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.app-sandbox</key>
|
<key>com.apple.security.app-sandbox</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.net.shadowfacts.Reader</string>
|
||||||
|
</array>
|
||||||
<key>com.apple.security.network.client</key>
|
<key>com.apple.security.network.client</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import OSLog
|
import OSLog
|
||||||
|
import Persistence
|
||||||
|
|
||||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
|
|
||||||
|
@ -94,6 +95,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
Task(priority: .userInitiated) {
|
Task(priority: .userInitiated) {
|
||||||
await fervorController.syncReadToServer()
|
await fervorController.syncReadToServer()
|
||||||
}
|
}
|
||||||
|
Task(priority: .userInitiated) {
|
||||||
|
await WidgetHelper.updateWidgetData(fervorController: fervorController)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,6 +122,36 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
|
||||||
|
let url = URLContexts.first!.url
|
||||||
|
guard url.scheme == "reader",
|
||||||
|
url.host == "item",
|
||||||
|
let itemID = url.pathComponents.last else {
|
||||||
|
logger.error("Launched with unknown URL: \(url.absoluteString, privacy: .public)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let split = window?.rootViewController as? AppSplitViewController else {
|
||||||
|
logger.error("Could not handle URL: missing split VC")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let req = Item.fetchRequest()
|
||||||
|
req.predicate = NSPredicate(format: "id = %@", itemID)
|
||||||
|
if let items = try? fervorController.persistentContainer.viewContext.fetch(req),
|
||||||
|
let item = items.first {
|
||||||
|
split.showItem(item)
|
||||||
|
Task {
|
||||||
|
await fervorController.markItem(item, read: true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Task {
|
||||||
|
if let item = try? await fervorController.fetchItem(id: itemID) {
|
||||||
|
split.showItem(item)
|
||||||
|
await fervorController.markItem(item, read: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func setupUI(from activity: NSUserActivity) async {
|
private func setupUI(from activity: NSUserActivity) async {
|
||||||
guard let split = window?.rootViewController as? AppSplitViewController else {
|
guard let split = window?.rootViewController as? AppSplitViewController else {
|
||||||
logger.error("Failed to setup UI for user activity: missing split VC")
|
logger.error("Failed to setup UI for user activity: missing split VC")
|
||||||
|
@ -185,7 +219,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
// todo: why the fuck doesn't this work
|
// todo: why the fuck doesn't this work
|
||||||
// it always picks the most recently focused window
|
// it always picks the most recently focused window
|
||||||
scene.activationConditions.prefersToActivateForTargetContentIdentifierPredicate = NSPredicate(format: "self == '\(account.id.base64EncodedString())'")
|
scene.activationConditions.prefersToActivateForTargetContentIdentifierPredicate = NSPredicate(format: "self == '\(account.id.base64EncodedString())'")
|
||||||
scene.activationConditions.canActivateForTargetContentIdentifierPredicate = NSPredicate(value: false)
|
scene.activationConditions.canActivateForTargetContentIdentifierPredicate = NSPredicate(value: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func syncFromServer() {
|
private func syncFromServer() {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import UIKit
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
import AppKit
|
import AppKit
|
||||||
#endif
|
#endif
|
||||||
|
import Persistence
|
||||||
|
|
||||||
class AppSplitViewController: UISplitViewController {
|
class AppSplitViewController: UISplitViewController {
|
||||||
|
|
||||||
|
@ -86,6 +87,7 @@ class AppSplitViewController: UISplitViewController {
|
||||||
home.selectItem(type)
|
home.selectItem(type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
func showItem(_ item: Item) -> ReadViewController {
|
func showItem(_ item: Item) -> ReadViewController {
|
||||||
if traitCollection.horizontalSizeClass == .compact {
|
if traitCollection.horizontalSizeClass == .compact {
|
||||||
let nav = viewController(for: .compact) as! UINavigationController
|
let nav = viewController(for: .compact) as! UINavigationController
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Fervor
|
||||||
|
|
||||||
class HomeCollectionViewCell: UICollectionViewListCell {
|
class HomeCollectionViewCell: UICollectionViewListCell {
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import CoreData
|
import CoreData
|
||||||
import Combine
|
import Combine
|
||||||
|
import Persistence
|
||||||
|
|
||||||
protocol HomeViewControllerDelegate: AnyObject {
|
protocol HomeViewControllerDelegate: AnyObject {
|
||||||
func switchToAccount(_ account: LocalData.Account)
|
func switchToAccount(_ account: LocalData.Account)
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Persistence
|
||||||
|
|
||||||
protocol ItemCollectionViewCellDelegate: AnyObject {
|
protocol ItemCollectionViewCellDelegate: AnyObject {
|
||||||
var fervorController: FervorController { get }
|
var fervorController: FervorController { get }
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import CoreData
|
import CoreData
|
||||||
import SafariServices
|
import SafariServices
|
||||||
|
import Persistence
|
||||||
|
|
||||||
protocol ItemsViewControllerDelegate: AnyObject {
|
protocol ItemsViewControllerDelegate: AnyObject {
|
||||||
func showReadItem(_ item: Item)
|
func showReadItem(_ item: Item)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import WebKit
|
||||||
import HTMLEntities
|
import HTMLEntities
|
||||||
import SafariServices
|
import SafariServices
|
||||||
import Combine
|
import Combine
|
||||||
|
import Persistence
|
||||||
|
|
||||||
class ReadViewController: UIViewController {
|
class ReadViewController: UIViewController {
|
||||||
|
|
||||||
|
@ -60,7 +61,8 @@ class ReadViewController: UIViewController {
|
||||||
navigationItem.largeTitleDisplayMode = .never
|
navigationItem.largeTitleDisplayMode = .never
|
||||||
|
|
||||||
view.backgroundColor = .appBackground
|
view.backgroundColor = .appBackground
|
||||||
view.addInteraction(StretchyMenuInteraction(delegate: self))
|
let menuInteraction = StretchyMenuInteraction(delegate: self)
|
||||||
|
view.addInteraction(menuInteraction)
|
||||||
|
|
||||||
webView = WKWebView()
|
webView = WKWebView()
|
||||||
webView.translatesAutoresizingMaskIntoConstraints = false
|
webView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
@ -76,6 +78,8 @@ class ReadViewController: UIViewController {
|
||||||
if let content = itemContentHTML() {
|
if let content = itemContentHTML() {
|
||||||
webView.loadHTMLString(content, baseURL: item.url)
|
webView.loadHTMLString(content, baseURL: item.url)
|
||||||
}
|
}
|
||||||
|
webView.scrollView.alwaysBounceHorizontal = false
|
||||||
|
webView.scrollView.panGestureRecognizer.require(toFail: menuInteraction.panRecognizer!)
|
||||||
view.addSubview(webView)
|
view.addSubview(webView)
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
|
|
|
@ -23,6 +23,7 @@ class StretchyMenuInteraction: NSObject, UIInteraction {
|
||||||
weak var delegate: StretchyMenuInteractionDelegate?
|
weak var delegate: StretchyMenuInteractionDelegate?
|
||||||
|
|
||||||
private(set) weak var view: UIView? = nil
|
private(set) weak var view: UIView? = nil
|
||||||
|
private(set) var panRecognizer: UIPanGestureRecognizer?
|
||||||
|
|
||||||
private let menuHintView = MenuHintView()
|
private let menuHintView = MenuHintView()
|
||||||
fileprivate let feedbackGenerator = UIImpactFeedbackGenerator(style: .medium)
|
fileprivate let feedbackGenerator = UIImpactFeedbackGenerator(style: .medium)
|
||||||
|
@ -56,7 +57,7 @@ class StretchyMenuInteraction: NSObject, UIInteraction {
|
||||||
panRecognizer.delegate = self
|
panRecognizer.delegate = self
|
||||||
panRecognizer.allowedScrollTypesMask = [.continuous]
|
panRecognizer.allowedScrollTypesMask = [.continuous]
|
||||||
view.addGestureRecognizer(panRecognizer)
|
view.addGestureRecognizer(panRecognizer)
|
||||||
|
self.panRecognizer = panRecognizer
|
||||||
}
|
}
|
||||||
|
|
||||||
private var prevTranslation: CGFloat = 0
|
private var prevTranslation: CGFloat = 0
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Persistence
|
||||||
|
|
||||||
extension NSUserActivity {
|
extension NSUserActivity {
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
//
|
||||||
|
// WidgetData.swift
|
||||||
|
// Reader
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 6/19/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Persistence
|
||||||
|
|
||||||
|
struct WidgetData: Codable {
|
||||||
|
let recentItems: [Item]
|
||||||
|
|
||||||
|
private static func url(for account: LocalData.Account) -> URL {
|
||||||
|
let groupContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.net.shadowfacts.Reader")!
|
||||||
|
let appSupport = groupContainerURL
|
||||||
|
.appendingPathComponent("Library", isDirectory: true)
|
||||||
|
.appendingPathComponent("Application Support", isDirectory: true)
|
||||||
|
let widgetData = appSupport.appendingPathComponent("Widget Data", isDirectory: true)
|
||||||
|
return widgetData
|
||||||
|
.appendingPathComponent(account.persistenceKey)
|
||||||
|
.appendingPathExtension("plist")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func load(account: LocalData.Account) -> WidgetData {
|
||||||
|
let url = url(for: account)
|
||||||
|
let decoder = PropertyListDecoder()
|
||||||
|
guard let data = try? Data(contentsOf: url),
|
||||||
|
let decoded = try? decoder.decode(WidgetData.self, from: data) else {
|
||||||
|
return WidgetData(recentItems: [])
|
||||||
|
}
|
||||||
|
return decoded
|
||||||
|
}
|
||||||
|
|
||||||
|
func save(account: LocalData.Account) {
|
||||||
|
let encoder = PropertyListEncoder()
|
||||||
|
let data = try! encoder.encode(self)
|
||||||
|
let url = WidgetData.url(for: account)
|
||||||
|
try! FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true)
|
||||||
|
try! data.write(to: url, options: .noFileProtection)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Item: Codable, Identifiable {
|
||||||
|
let id: String
|
||||||
|
let feedTitle: String?
|
||||||
|
let title: String?
|
||||||
|
let published: Date?
|
||||||
|
|
||||||
|
var launchURL: URL {
|
||||||
|
return URL(string: "reader://item/\(id)")!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
//
|
||||||
|
// WidgetHelper.swift
|
||||||
|
// Reader
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 6/19/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import WidgetKit
|
||||||
|
import Persistence
|
||||||
|
|
||||||
|
struct WidgetHelper {
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
private static let maxDisplayableItems = 8
|
||||||
|
|
||||||
|
static func updateWidgetData(fervorController: FervorController) async {
|
||||||
|
// Accessing CoreData from the widget extension puts us over the memory limit, so we pre-generate all the data it needs and save it to disk
|
||||||
|
|
||||||
|
let prioritizedItems: [WidgetData.Item] = await fervorController.persistentContainer.performBackgroundTask { ctx in
|
||||||
|
let req = Item.fetchRequest()
|
||||||
|
req.sortDescriptors = [NSSortDescriptor(key: "published", ascending: false)]
|
||||||
|
req.fetchLimit = 32
|
||||||
|
req.predicate = NSPredicate(format: "read = NO")
|
||||||
|
var items = (try? ctx.fetch(req)) ?? []
|
||||||
|
|
||||||
|
var prioritizedItems: [Item] = []
|
||||||
|
|
||||||
|
while prioritizedItems.count < maxDisplayableItems {
|
||||||
|
let firstFromUnseenFeedIdx = items.firstIndex { item in
|
||||||
|
prioritizedItems.allSatisfy { existing in
|
||||||
|
existing.feed != item.feed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let firstFromUnseenFeedIdx {
|
||||||
|
prioritizedItems.append(items.remove(at: firstFromUnseenFeedIdx))
|
||||||
|
} else if let item = items.first {
|
||||||
|
prioritizedItems.append(item)
|
||||||
|
items.removeFirst()
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prioritizedItems.map {
|
||||||
|
WidgetData.Item(id: $0.id!, feedTitle: $0.feed?.title, title: $0.title, published: $0.published)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WidgetData(recentItems: prioritizedItems).save(account: fervorController.account!)
|
||||||
|
|
||||||
|
WidgetCenter.shared.reloadAllTimelines()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>NSExtension</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
<string>com.apple.widgetkit-extension</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,196 @@
|
||||||
|
//
|
||||||
|
// Recents.swift
|
||||||
|
// Reader
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 6/19/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import WidgetKit
|
||||||
|
import SwiftUI
|
||||||
|
import Persistence
|
||||||
|
|
||||||
|
struct RecentsProvider: IntentTimelineProvider {
|
||||||
|
func placeholder(in context: Context) -> RecentsEntry {
|
||||||
|
RecentsEntry(items: [], configuration: ConfigurationIntent())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (RecentsEntry) -> ()) {
|
||||||
|
let entry = RecentsEntry(items: getItems(), configuration: configuration)
|
||||||
|
completion(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<RecentsEntry>) -> ()) {
|
||||||
|
// TODO: get account from configuration intent
|
||||||
|
|
||||||
|
let entry = RecentsEntry(items: getItems(), configuration: configuration)
|
||||||
|
let refreshDate = Calendar.current.date(byAdding: .hour, value: 1, to: Date())!
|
||||||
|
let timeline = Timeline(entries: [entry], policy: .after(refreshDate))
|
||||||
|
completion(timeline)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getItems() -> [WidgetData.Item] {
|
||||||
|
guard let account = LocalData.mostRecentAccount() else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return WidgetData.load(account: account).recentItems
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RecentsEntry: TimelineEntry {
|
||||||
|
let date = Date()
|
||||||
|
|
||||||
|
let items: [WidgetData.Item]
|
||||||
|
let configuration: ConfigurationIntent
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RecentsEntryView: View {
|
||||||
|
let entry: RecentsEntry
|
||||||
|
|
||||||
|
@Environment(\.widgetFamily) var family
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if entry.items.isEmpty {
|
||||||
|
} else {
|
||||||
|
switch family {
|
||||||
|
case .systemSmall:
|
||||||
|
SquareItemView(item: entry.items[0])
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
case .systemMedium, .systemLarge:
|
||||||
|
VStack {
|
||||||
|
ForEach(Array(entry.items.prefix(family.maxItemCount).enumerated()), id: \.element.id) { (index, item) in
|
||||||
|
if index != 0 {
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
ItemListEntryView(item: item)
|
||||||
|
Spacer(minLength: 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
case .systemExtraLarge:
|
||||||
|
if #available(iOS 16.0, *) {
|
||||||
|
VStack {
|
||||||
|
ForEach(Array(stride(from: 0, to: min(entry.items.count, family.maxItemCount), by: 2)), id: \.self) { idx in
|
||||||
|
HStack {
|
||||||
|
ItemListEntryView(item: entry.items[idx])
|
||||||
|
if idx + 1 < entry.items.count {
|
||||||
|
Divider()
|
||||||
|
ItemListEntryView(item: entry.items[idx + 1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if idx + 2 < entry.items.count {
|
||||||
|
Spacer()
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
} else {
|
||||||
|
Text("Requires iOS 16")
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
fatalError("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension WidgetFamily {
|
||||||
|
var maxItemCount: Int {
|
||||||
|
switch self {
|
||||||
|
case .systemSmall:
|
||||||
|
return 1
|
||||||
|
case .systemMedium:
|
||||||
|
return 2
|
||||||
|
case .systemLarge:
|
||||||
|
return 4
|
||||||
|
case .systemExtraLarge:
|
||||||
|
return 8
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var feedFont = Font.subheadline.weight(.medium).italic()
|
||||||
|
private var titleUIFont: UIFont {
|
||||||
|
// TODO: this should use the compressed SF Pro variant, but there's no API to get at it
|
||||||
|
let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .headline).withSymbolicTraits(.traitCondensed)!
|
||||||
|
return UIFont(descriptor: descriptor, size: 0)
|
||||||
|
}
|
||||||
|
private var titleFont = Font(titleUIFont).leading(.tight)
|
||||||
|
|
||||||
|
struct SquareItemView: View {
|
||||||
|
let item: WidgetData.Item
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
HStack {
|
||||||
|
Text(verbatim: item.feedTitle ?? "")
|
||||||
|
.font(feedFont)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
// force the vstack to be as wide as possible
|
||||||
|
Spacer(minLength: 0)
|
||||||
|
}
|
||||||
|
Text(verbatim: item.title ?? "")
|
||||||
|
.font(titleFont)
|
||||||
|
if let published = item.published {
|
||||||
|
Text(published, format: .relative(presentation: .numeric, unitsStyle: .narrow))
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
Spacer(minLength: 0)
|
||||||
|
}
|
||||||
|
.widgetURL(item.launchURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ItemListEntryView: View {
|
||||||
|
let item: WidgetData.Item
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Link(destination: item.launchURL) {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
HStack {
|
||||||
|
Text(verbatim: item.feedTitle ?? "")
|
||||||
|
.font(feedFont)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
Spacer()
|
||||||
|
if let published = item.published {
|
||||||
|
Text(published, format: .relative(presentation: .numeric, unitsStyle: .narrow))
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(verbatim: item.title ?? "")
|
||||||
|
.font(titleFont)
|
||||||
|
.lineLimit(3)
|
||||||
|
Spacer(minLength: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Recents_Previews: PreviewProvider {
|
||||||
|
static let item1 = WidgetData.Item(
|
||||||
|
id: "1",
|
||||||
|
feedTitle: "Daring Fireball",
|
||||||
|
title: "There's a Privacy Angle on Apple's Decision to Finance Apple Pay Layer On Its Own",
|
||||||
|
published: Calendar.current.date(byAdding: .hour, value: -1, to: Date())!
|
||||||
|
)
|
||||||
|
static let item2 = WidgetData.Item(
|
||||||
|
id: "2",
|
||||||
|
feedTitle: "Ars Technica",
|
||||||
|
title: "Senate bill would ban data brokers from selling location and health data",
|
||||||
|
published: Calendar.current.date(byAdding: .hour, value: -2, to: Date())!
|
||||||
|
)
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
// RecentsEntryView(entry: RecentsEntry(items: [item1], configuration: ConfigurationIntent()))
|
||||||
|
// .previewContext(WidgetPreviewContext(family: .systemSmall))
|
||||||
|
|
||||||
|
RecentsEntryView(entry: RecentsEntry(items: [item1, item2], configuration: ConfigurationIntent()))
|
||||||
|
.previewContext(WidgetPreviewContext(family: .systemMedium))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>INEnums</key>
|
||||||
|
<array/>
|
||||||
|
<key>INIntentDefinitionModelVersion</key>
|
||||||
|
<string>1.2</string>
|
||||||
|
<key>INIntentDefinitionNamespace</key>
|
||||||
|
<string>88xZPY</string>
|
||||||
|
<key>INIntentDefinitionSystemVersion</key>
|
||||||
|
<string>22A5266r</string>
|
||||||
|
<key>INIntentDefinitionToolsBuildVersion</key>
|
||||||
|
<string>14A5228q</string>
|
||||||
|
<key>INIntentDefinitionToolsVersion</key>
|
||||||
|
<string>14.0</string>
|
||||||
|
<key>INIntents</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>INIntentCategory</key>
|
||||||
|
<string>information</string>
|
||||||
|
<key>INIntentDescriptionID</key>
|
||||||
|
<string>tVvJ9c</string>
|
||||||
|
<key>INIntentEligibleForWidgets</key>
|
||||||
|
<true/>
|
||||||
|
<key>INIntentIneligibleForSuggestions</key>
|
||||||
|
<true/>
|
||||||
|
<key>INIntentLastParameterTag</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
<key>INIntentName</key>
|
||||||
|
<string>Configuration</string>
|
||||||
|
<key>INIntentResponse</key>
|
||||||
|
<dict>
|
||||||
|
<key>INIntentResponseCodes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>INIntentResponseCodeName</key>
|
||||||
|
<string>success</string>
|
||||||
|
<key>INIntentResponseCodeSuccess</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>INIntentResponseCodeName</key>
|
||||||
|
<string>failure</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<key>INIntentTitle</key>
|
||||||
|
<string>Configuration</string>
|
||||||
|
<key>INIntentTitleID</key>
|
||||||
|
<string>gpCwrM</string>
|
||||||
|
<key>INIntentType</key>
|
||||||
|
<string>Custom</string>
|
||||||
|
<key>INIntentVerb</key>
|
||||||
|
<string>View</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>INTypes</key>
|
||||||
|
<array/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
// Widgets.swift
|
||||||
|
// Widgets
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 6/19/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import WidgetKit
|
||||||
|
import SwiftUI
|
||||||
|
import Intents
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct Widgets: Widget {
|
||||||
|
var body: some WidgetConfiguration {
|
||||||
|
IntentConfiguration(kind: "recents", intent: ConfigurationIntent.self, provider: RecentsProvider()) { entry in
|
||||||
|
RecentsEntryView(entry: entry)
|
||||||
|
}
|
||||||
|
.configurationDisplayName("Recent Articles")
|
||||||
|
.description("Recently posted articles from your feeds.")
|
||||||
|
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge, .systemExtraLarge])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.net.shadowfacts.Reader</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
Loading…
Reference in New Issue