Move CoreData stack to separate Swift package
This commit is contained in:
parent
5d625ca844
commit
14dca95610
|
@ -5,6 +5,9 @@ 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(
|
||||
|
|
|
@ -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)
|
||||
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 }
|
||||
self.id = serverFeed.id
|
||||
self.title = serverFeed.title
|
|
@ -13,12 +13,11 @@ import Fervor
|
|||
@objc(Group)
|
||||
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 }
|
||||
self.id = serverGroup.id
|
||||
self.title = serverGroup.title
|
||||
// feeds relationships will be updated after feeds are created in PersistentContainer.sync
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -13,7 +13,7 @@ import Fervor
|
|||
@objc(Item)
|
||||
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 }
|
||||
self.id = serverItem.id
|
||||
self.author = serverItem.author
|
||||
|
@ -29,5 +29,4 @@ public class Item: NSManagedObject {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -9,16 +9,16 @@ import Foundation
|
|||
import Fervor
|
||||
import CryptoKit
|
||||
|
||||
struct LocalData {
|
||||
public struct LocalData {
|
||||
|
||||
private init() {}
|
||||
|
||||
private static let encoder = JSONEncoder()
|
||||
private static let decoder = JSONDecoder()
|
||||
|
||||
private static var defaults = UserDefaults(suiteName: "group.net.shadowfacts.Reader")!
|
||||
public static var defaults = UserDefaults(suiteName: "group.net.shadowfacts.Reader")!
|
||||
|
||||
static var accounts: [Account] {
|
||||
public static var accounts: [Account] {
|
||||
get {
|
||||
guard let data = defaults.data(forKey: "accounts"),
|
||||
let accounts = try? decoder.decode([Account].self, from: data) else {
|
||||
|
@ -32,7 +32,7 @@ struct LocalData {
|
|||
}
|
||||
}
|
||||
|
||||
static var mostRecentAccountID: Data? {
|
||||
public static var mostRecentAccountID: Data? {
|
||||
get {
|
||||
return defaults.data(forKey: "mostRecentAccountID")
|
||||
}
|
||||
|
@ -41,36 +41,25 @@ struct LocalData {
|
|||
}
|
||||
}
|
||||
|
||||
static func mostRecentAccount() -> Account? {
|
||||
public static func mostRecentAccount() -> Account? {
|
||||
guard let id = mostRecentAccountID else {
|
||||
return nil
|
||||
}
|
||||
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 })
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
public struct Account: Codable {
|
||||
public let id: Data
|
||||
public let instanceURL: URL
|
||||
public let clientID: String
|
||||
public let clientSecret: String
|
||||
public let token: Token
|
||||
|
||||
struct Account: Codable {
|
||||
let id: Data
|
||||
let instanceURL: URL
|
||||
let clientID: String
|
||||
let clientSecret: String
|
||||
let token: Token
|
||||
|
||||
init(instanceURL: URL, clientID: String, clientSecret: String, token: Token) {
|
||||
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
|
||||
// user activites can uniquely identify accounts across devices
|
||||
var hasher = SHA256()
|
|
@ -10,10 +10,10 @@ import Fervor
|
|||
import OSLog
|
||||
|
||||
// todo: is this actually sendable?
|
||||
class PersistentContainer: NSPersistentContainer, @unchecked Sendable {
|
||||
public class PersistentContainer: NSPersistentContainer, @unchecked Sendable {
|
||||
|
||||
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)!
|
||||
}()
|
||||
|
||||
|
@ -24,11 +24,9 @@ class PersistentContainer: NSPersistentContainer, @unchecked Sendable {
|
|||
return context
|
||||
}()
|
||||
|
||||
weak var fervorController: FervorController?
|
||||
|
||||
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
|
||||
let name = account.id.base64EncodedString().replacingOccurrences(of: "/", with: "_")
|
||||
super.init(name: name, managedObjectModel: PersistentContainer.managedObjectModel)
|
||||
|
@ -68,20 +66,20 @@ class PersistentContainer: NSPersistentContainer, @unchecked Sendable {
|
|||
}
|
||||
|
||||
@MainActor
|
||||
func saveViewContext() throws {
|
||||
public func saveViewContext() throws {
|
||||
if viewContext.hasChanges {
|
||||
try viewContext.save()
|
||||
}
|
||||
}
|
||||
|
||||
func lastSyncDate() async throws -> Date? {
|
||||
public func lastSyncDate() async throws -> Date? {
|
||||
return try await backgroundContext.perform {
|
||||
let state = try self.backgroundContext.fetch(SyncState.fetchRequest()).first
|
||||
return state?.lastSync
|
||||
}
|
||||
}
|
||||
|
||||
func updateLastSyncDate(_ date: Date) async throws {
|
||||
public func updateLastSyncDate(_ date: Date) async throws {
|
||||
try await backgroundContext.perform {
|
||||
if let state = try self.backgroundContext.fetch(SyncState.fetchRequest()).first {
|
||||
state.lastSync = date
|
||||
|
@ -95,7 +93,7 @@ class PersistentContainer: NSPersistentContainer, @unchecked Sendable {
|
|||
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 {
|
||||
let existingGroups = try self.backgroundContext.fetch(Group.fetchRequest())
|
||||
for group in serverGroups {
|
||||
|
@ -130,7 +128,7 @@ class PersistentContainer: NSPersistentContainer, @unchecked Sendable {
|
|||
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 {
|
||||
self.logger.debug("syncItems: deleting \(syncUpdate.delete.count, privacy: .public) items")
|
||||
let deleteReq = Item.fetchRequest()
|
||||
|
@ -151,7 +149,7 @@ class PersistentContainer: NSPersistentContainer, @unchecked Sendable {
|
|||
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
|
||||
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 }) {
|
||||
existing.updateFromServer(item)
|
||||
} else {
|
|
@ -1,5 +1,5 @@
|
|||
<?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">
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
|
@ -10,7 +10,6 @@
|
|||
D608238D27DE729E00D7D5F9 /* ItemListType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D608238C27DE729E00D7D5F9 /* ItemListType.swift */; };
|
||||
D65B18B22750469D004A9448 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18B12750469D004A9448 /* LoginViewController.swift */; };
|
||||
D65B18B627504920004A9448 /* FervorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18B527504920004A9448 /* FervorController.swift */; };
|
||||
D65B18BE275051A1004A9448 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18BD275051A1004A9448 /* LocalData.swift */; };
|
||||
D65B18C127505348004A9448 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18C027505348004A9448 /* HomeViewController.swift */; };
|
||||
D68408E827947D0800E327D2 /* PrefsSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68408E727947D0800E327D2 /* PrefsSceneDelegate.swift */; };
|
||||
D68408ED2794803D00E327D2 /* PrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68408EC2794803D00E327D2 /* PrefsView.swift */; };
|
||||
|
@ -21,22 +20,14 @@
|
|||
D68B303D2792204B00E8B3FA /* read.js in Resources */ = {isa = PBXBuildFile; fileRef = D68B303C2792204B00E8B3FA /* read.js */; };
|
||||
D68B30402792729A00E8B3FA /* AppSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68B303F2792729A00E8B3FA /* AppSplitViewController.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 */; };
|
||||
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 */; };
|
||||
D6C687FB272CD27700874C10 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6C687F9272CD27700874C10 /* LaunchScreen.storyboard */; };
|
||||
D6C68806272CD27700874C10 /* ReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C68805272CD27700874C10 /* ReaderTests.swift */; };
|
||||
D6C68810272CD27700874C10 /* ReaderUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C6880F272CD27700874C10 /* ReaderUITests.swift */; };
|
||||
D6C68812272CD27700874C10 /* ReaderUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C68811272CD27700874C10 /* ReaderUITestsLaunchTests.swift */; };
|
||||
D6E2434C278B456A0005E546 /* ItemsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2434B278B456A0005E546 /* ItemsViewController.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 */; };
|
||||
D6E24369278BABB40005E546 /* UIColor+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E24368278BABB40005E546 /* UIColor+App.swift */; };
|
||||
D6E2436B278BB1880005E546 /* HomeCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2436A278BB1880005E546 /* HomeCollectionViewCell.swift */; };
|
||||
|
@ -45,6 +36,9 @@
|
|||
D6E24373278BE2B80005E546 /* read.css in Resources */ = {isa = PBXBuildFile; fileRef = D6E24372278BE2B80005E546 /* read.css */; };
|
||||
D6EB531D278C89C300AD2E61 /* AppNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EB531C278C89C300AD2E61 /* AppNavigationController.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 */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -99,7 +93,6 @@
|
|||
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>"; };
|
||||
D65B18B527504920004A9448 /* FervorController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FervorController.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>"; };
|
||||
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>"; };
|
||||
|
@ -113,12 +106,11 @@
|
|||
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>"; };
|
||||
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; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -128,12 +120,6 @@
|
|||
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>"; };
|
||||
D6E2434B278B456A0005E546 /* ItemsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsViewController.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>"; };
|
||||
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>"; };
|
||||
|
@ -141,6 +127,7 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -155,6 +142,8 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D6EEDE89285FA75F009F854E /* Persistence in Frameworks */,
|
||||
D6EEDE87285FA75D009F854E /* Fervor in Frameworks */,
|
||||
D6E24371278BE1250005E546 /* HTMLEntities in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -231,24 +220,10 @@
|
|||
name = Frameworks;
|
||||
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 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D6AB5E9B285F706F00157F2F /* Persistence */,
|
||||
D6AB5E9A285F6FE100157F2F /* Fervor */,
|
||||
D6C687EA272CD27600874C10 /* Reader */,
|
||||
D6C68804272CD27700874C10 /* ReaderTests */,
|
||||
|
@ -279,14 +254,13 @@
|
|||
D6C687ED272CD27600874C10 /* SceneDelegate.swift */,
|
||||
D68408E727947D0800E327D2 /* PrefsSceneDelegate.swift */,
|
||||
D65B18B527504920004A9448 /* FervorController.swift */,
|
||||
D65B18BD275051A1004A9448 /* LocalData.swift */,
|
||||
D6E24368278BABB40005E546 /* UIColor+App.swift */,
|
||||
D6EB531E278E4A7500AD2E61 /* StretchyMenuInteraction.swift */,
|
||||
D68B303527907D9200E8B3FA /* ExcerptGenerator.swift */,
|
||||
D68B304127932ED500E8B3FA /* UserActivities.swift */,
|
||||
D68408EE2794808E00E327D2 /* Preferences.swift */,
|
||||
D608238C27DE729E00D7D5F9 /* ItemListType.swift */,
|
||||
D6A8A33527766E9300CCEC72 /* CoreData */,
|
||||
D6EEDE8A285FA7FD009F854E /* LocalData+Migration.swift */,
|
||||
D65B18AF2750468B004A9448 /* Screens */,
|
||||
D6C687F7272CD27700874C10 /* Assets.xcassets */,
|
||||
D6C687F9272CD27700874C10 /* LaunchScreen.storyboard */,
|
||||
|
@ -370,6 +344,8 @@
|
|||
name = Reader;
|
||||
packageProductDependencies = (
|
||||
D6E24370278BE1250005E546 /* HTMLEntities */,
|
||||
D6EEDE86285FA75D009F854E /* Fervor */,
|
||||
D6EEDE88285FA75F009F854E /* Persistence */,
|
||||
);
|
||||
productName = Reader;
|
||||
productReference = D6C687E8272CD27600874C10 /* Reader.app */;
|
||||
|
@ -532,26 +508,18 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D6A8A33427766C2800CCEC72 /* PersistentContainer.swift in Sources */,
|
||||
D6E24357278B96E40005E546 /* Feed+CoreDataClass.swift in Sources */,
|
||||
D608238D27DE729E00D7D5F9 /* ItemListType.swift in Sources */,
|
||||
D65B18B627504920004A9448 /* FervorController.swift in Sources */,
|
||||
D68B304227932ED500E8B3FA /* UserActivities.swift in Sources */,
|
||||
D6C687EC272CD27600874C10 /* AppDelegate.swift in Sources */,
|
||||
D6C687F6272CD27600874C10 /* Reader.xcdatamodeld in Sources */,
|
||||
D6E2436B278BB1880005E546 /* HomeCollectionViewCell.swift in Sources */,
|
||||
D6E2435F278B97240005E546 /* Group+CoreDataClass.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 */,
|
||||
D6EB531D278C89C300AD2E61 /* AppNavigationController.swift in Sources */,
|
||||
D6E24360278B97240005E546 /* Group+CoreDataProperties.swift in Sources */,
|
||||
D6E2434C278B456A0005E546 /* ItemsViewController.swift in Sources */,
|
||||
D6E2435E278B97240005E546 /* Item+CoreDataProperties.swift in Sources */,
|
||||
D6EB531F278E4A7500AD2E61 /* StretchyMenuInteraction.swift in Sources */,
|
||||
D6E24358278B96E40005E546 /* Feed+CoreDataProperties.swift in Sources */,
|
||||
D68B30402792729A00E8B3FA /* AppSplitViewController.swift in Sources */,
|
||||
D65B18BE275051A1004A9448 /* LocalData.swift in Sources */,
|
||||
D65B18B22750469D004A9448 /* LoginViewController.swift in Sources */,
|
||||
D68408ED2794803D00E327D2 /* PrefsView.swift in Sources */,
|
||||
D68408EF2794808E00E327D2 /* Preferences.swift in Sources */,
|
||||
|
@ -1021,20 +989,15 @@
|
|||
package = D6E2436F278BE1250005E546 /* XCRemoteSwiftPackageReference "swift-html-entities" */;
|
||||
productName = HTMLEntities;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
||||
/* Begin XCVersionGroup section */
|
||||
D6C687F4272CD27600874C10 /* Reader.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
D6C687F5272CD27600874C10 /* Reader.xcdatamodel */,
|
||||
);
|
||||
currentVersion = D6C687F5272CD27600874C10 /* Reader.xcdatamodel */;
|
||||
path = Reader.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
D6EEDE86285FA75D009F854E /* Fervor */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Fervor;
|
||||
};
|
||||
/* End XCVersionGroup section */
|
||||
D6EEDE88285FA75F009F854E /* Persistence */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Persistence;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = D6C687E0272CD27600874C10 /* Project object */;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import UIKit
|
|||
import WebKit
|
||||
import OSLog
|
||||
import Combine
|
||||
import Persistence
|
||||
|
||||
@main
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import Foundation
|
||||
import OSLog
|
||||
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 struct ExcerptGenerator {
|
||||
|
|
|
@ -9,6 +9,7 @@ import Foundation
|
|||
import Fervor
|
||||
import OSLog
|
||||
import Combine
|
||||
import Persistence
|
||||
|
||||
actor FervorController {
|
||||
|
||||
|
@ -42,8 +43,6 @@ actor FervorController {
|
|||
} else {
|
||||
self.persistentContainer = nil
|
||||
}
|
||||
|
||||
persistentContainer?.fervorController = self
|
||||
}
|
||||
|
||||
convenience init(account: LocalData.Account) async {
|
||||
|
@ -85,7 +84,7 @@ actor FervorController {
|
|||
let lastSync = try await persistentContainer.lastSyncDate()
|
||||
logger.info("Syncing items with last sync date: \(String(describing: lastSync), privacy: .public)")
|
||||
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)
|
||||
|
||||
setSyncState(.excerpts)
|
||||
|
@ -137,7 +136,7 @@ actor FervorController {
|
|||
}
|
||||
|
||||
@MainActor
|
||||
func markItem(_ item: Item, read: Bool) async {
|
||||
func markItem(_ item: Persistence.Item, read: Bool) async {
|
||||
item.read = read
|
||||
do {
|
||||
let f = read ? client.read(item:) : client.unread(item:)
|
||||
|
@ -156,11 +155,11 @@ actor FervorController {
|
|||
}
|
||||
|
||||
@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 {
|
||||
return nil
|
||||
}
|
||||
let item = Item(context: persistentContainer.viewContext)
|
||||
let item = Persistence.Item(context: persistentContainer.viewContext)
|
||||
item.updateFromServer(serverItem)
|
||||
try persistentContainer.saveViewContext()
|
||||
return item
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import Persistence
|
||||
|
||||
enum ItemListType: Hashable {
|
||||
case unread
|
||||
|
@ -43,8 +44,8 @@ enum ItemListType: Hashable {
|
|||
return req
|
||||
}
|
||||
|
||||
var countFetchRequest: NSFetchRequest<Reader.Item>? {
|
||||
let req = Reader.Item.fetchRequest()
|
||||
var countFetchRequest: NSFetchRequest<Item>? {
|
||||
let req = Item.fetchRequest()
|
||||
switch self {
|
||||
case .unread:
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import OSLog
|
||||
import Persistence
|
||||
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import UIKit
|
|||
#if targetEnvironment(macCatalyst)
|
||||
import AppKit
|
||||
#endif
|
||||
import Persistence
|
||||
|
||||
class AppSplitViewController: UISplitViewController {
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import Fervor
|
||||
|
||||
class HomeCollectionViewCell: UICollectionViewListCell {
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import UIKit
|
||||
import CoreData
|
||||
import Combine
|
||||
import Persistence
|
||||
|
||||
protocol HomeViewControllerDelegate: AnyObject {
|
||||
func switchToAccount(_ account: LocalData.Account)
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import Persistence
|
||||
|
||||
protocol ItemCollectionViewCellDelegate: AnyObject {
|
||||
var fervorController: FervorController { get }
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import UIKit
|
||||
import CoreData
|
||||
import SafariServices
|
||||
import Persistence
|
||||
|
||||
protocol ItemsViewControllerDelegate: AnyObject {
|
||||
func showReadItem(_ item: Item)
|
||||
|
|
|
@ -10,6 +10,7 @@ import WebKit
|
|||
import HTMLEntities
|
||||
import SafariServices
|
||||
import Combine
|
||||
import Persistence
|
||||
|
||||
class ReadViewController: UIViewController {
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import Persistence
|
||||
|
||||
extension NSUserActivity {
|
||||
|
||||
|
|
Loading…
Reference in New Issue