Compare commits
5 Commits
7d4f5ccba3
...
197edc5e1a
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 197edc5e1a | |
Shadowfacts | 6f3b8dc840 | |
Shadowfacts | 382826bfff | |
Shadowfacts | b79c8af12c | |
Shadowfacts | d906ec47f9 |
|
@ -5,7 +5,7 @@
|
||||||
// Created by Shadowfacts on 10/29/21.
|
// Created by Shadowfacts on 10/29/21.
|
||||||
//
|
//
|
||||||
|
|
||||||
@preconcurrency import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct Feed: Decodable, Sendable {
|
public struct Feed: Decodable, Sendable {
|
||||||
public let id: FervorID
|
public let id: FervorID
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// Created by Shadowfacts on 11/25/21.
|
// Created by Shadowfacts on 11/25/21.
|
||||||
//
|
//
|
||||||
|
|
||||||
@preconcurrency import Foundation
|
import Foundation
|
||||||
|
|
||||||
public actor FervorClient: Sendable {
|
public actor FervorClient: Sendable {
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// Created by Shadowfacts on 10/29/21.
|
// Created by Shadowfacts on 10/29/21.
|
||||||
//
|
//
|
||||||
|
|
||||||
@preconcurrency import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct Group: Decodable, Sendable {
|
public struct Group: Decodable, Sendable {
|
||||||
public let id: FervorID
|
public let id: FervorID
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// Created by Shadowfacts on 10/29/21.
|
// Created by Shadowfacts on 10/29/21.
|
||||||
//
|
//
|
||||||
|
|
||||||
@preconcurrency import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct Instance: Decodable, Sendable {
|
public struct Instance: Decodable, Sendable {
|
||||||
public let name: String
|
public let name: String
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// Created by Shadowfacts on 10/29/21.
|
// Created by Shadowfacts on 10/29/21.
|
||||||
//
|
//
|
||||||
|
|
||||||
@preconcurrency import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct Item: Decodable, Sendable {
|
public struct Item: Decodable, Sendable {
|
||||||
public let id: FervorID
|
public let id: FervorID
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// Created by Shadowfacts on 1/9/22.
|
// Created by Shadowfacts on 1/9/22.
|
||||||
//
|
//
|
||||||
|
|
||||||
@preconcurrency import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct ItemsSyncUpdate: Decodable, Sendable {
|
public struct ItemsSyncUpdate: Decodable, Sendable {
|
||||||
|
|
||||||
|
|
|
@ -508,7 +508,7 @@
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = 1;
|
BuildIndependentTargetsInParallel = 1;
|
||||||
LastSwiftUpdateCheck = 1320;
|
LastSwiftUpdateCheck = 1320;
|
||||||
LastUpgradeCheck = 1320;
|
LastUpgradeCheck = 1400;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
D684090C279486BF00E327D2 = {
|
D684090C279486BF00E327D2 = {
|
||||||
CreatedOnToolsVersion = 13.2;
|
CreatedOnToolsVersion = 13.2;
|
||||||
|
@ -616,7 +616,7 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/bash;
|
shellPath = /bin/bash;
|
||||||
shellScript = "pushd \"$PROJECT_DIR/lol-html/c-api/\"\n\nbuild() {\n echo \"Building lol-html with CARGO_TARGET: $1\"\n\n ~/.cargo/bin/cargo build --release --target $1\n}\n\nbuild_std() {\n echo \"Building lol-html with CARGO_TARGET: $1\"\n echo \"Building std enabled\"\n \n ~/.cargo/bin/cargo +nightly build -Z build-std=panic_abort,std --release --target $1\n}\n\nif [ \"$PLATFORM_NAME\" == \"iphonesimulator\" ]; then\n if [ \"$ARCHS\" == \"arm64\" ]; then\n build \"aarch64-apple-ios-sim\"\n elif [ \"$ARCHS\" == \"x86_64\" ]; then\n build \"x86_64-apple-ios\"\n else\n echo \"error: unknown value for \\$ARCHS\"\n exit 1\n fi\nelif [ \"$PLATFORM_NAME\" == \"iphoneos\" ]; then\n build \"aarch64-apple-ios\"\nelif [ \"$PLATFORM_NAME\" == \"macosx\" ]; then\n if grep -q \"arm64\" <<< \"$ARCHS\"; then\n build_std \"aarch64-apple-ios-macabi\"\n fi\n if grep -q \"x86_64\" <<< \"$ARCHS\"; then\n build_std \"x86_64-apple-ios-macabi\"\n fi\nelse\n echo \"error: unknown value for \\$PLATFORM_NAME\"\n exit 1\nfi\n";
|
shellScript = "pushd \"$PROJECT_DIR/lol-html/c-api/\"\n\nbuild() {\n echo \"Building lol-html with CARGO_TARGET: $1\"\n\n ~/.cargo/bin/cargo build --release --target $1\n}\n\nbuild_std() {\n echo \"Building lol-html with CARGO_TARGET: $1\"\n echo \"Building std enabled\"\n \n ~/.cargo/bin/cargo +nightly build -Z build-std=panic_abort,std --release --target $1\n}\n\nif [ \"$PLATFORM_NAME\" == \"iphonesimulator\" ]; then\n if [ \"$ARCHS\" == \"arm64\" ]; then\n build_std \"aarch64-apple-ios-sim\"\n elif [ \"$ARCHS\" == \"x86_64\" ]; then\n build \"x86_64-apple-ios\"\n else\n echo \"error: unknown value for \\$ARCHS\"\n exit 1\n fi\nelif [ \"$PLATFORM_NAME\" == \"iphoneos\" ]; then\n build_std \"aarch64-apple-ios\"\nelif [ \"$PLATFORM_NAME\" == \"macosx\" ]; then\n if grep -q \"arm64\" <<< \"$ARCHS\"; then\n build_std \"aarch64-apple-ios-macabi\"\n fi\n if grep -q \"x86_64\" <<< \"$ARCHS\"; then\n build_std \"x86_64-apple-ios-macabi\"\n fi\nelse\n echo \"error: unknown value for \\$PLATFORM_NAME\"\n exit 1\nfi\n";
|
||||||
};
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
@ -743,6 +743,7 @@
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_TEAM = ZPBBSK8L8B;
|
DEVELOPMENT_TEAM = ZPBBSK8L8B;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
@ -773,6 +774,7 @@
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_TEAM = ZPBBSK8L8B;
|
DEVELOPMENT_TEAM = ZPBBSK8L8B;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1320"
|
LastUpgradeVersion = "1400"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
// Created by Shadowfacts on 11/25/21.
|
// Created by Shadowfacts on 11/25/21.
|
||||||
//
|
//
|
||||||
|
|
||||||
@preconcurrency import Foundation
|
import Foundation
|
||||||
import Fervor
|
import Fervor
|
||||||
@preconcurrency import OSLog
|
import OSLog
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
actor FervorController {
|
actor FervorController {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// Created by Shadowfacts on 10/29/21.
|
// Created by Shadowfacts on 10/29/21.
|
||||||
//
|
//
|
||||||
|
|
||||||
@preconcurrency import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import OSLog
|
import OSLog
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,6 @@ class AppSplitViewController: UISplitViewController {
|
||||||
let nav = viewController(for: .compact) as! UINavigationController
|
let nav = viewController(for: .compact) as! UINavigationController
|
||||||
let top = nav.topViewController!
|
let top = nav.topViewController!
|
||||||
if top is ReadViewController || top is ItemsViewController {
|
if top is ReadViewController || top is ItemsViewController {
|
||||||
print(top.userActivity?.activityType)
|
|
||||||
return top.userActivity
|
return top.userActivity
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -75,21 +75,9 @@ class ItemsViewController: UIViewController {
|
||||||
])
|
])
|
||||||
|
|
||||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "published", ascending: false)]
|
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "published", ascending: false)]
|
||||||
do {
|
fetchItems()
|
||||||
let ids = try fervorController.persistentContainer.viewContext.fetch(fetchRequest)
|
|
||||||
|
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<Section, NSManagedObjectID>()
|
|
||||||
snapshot.appendSections([.items])
|
|
||||||
snapshot.appendItems(ids, toSection: .items)
|
|
||||||
dataSource.apply(snapshot, animatingDifferences: false)
|
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(managedObjectsDidChange), name: .NSManagedObjectContextObjectsDidChange, object: fervorController.persistentContainer.viewContext)
|
NotificationCenter.default.addObserver(self, selector: #selector(managedObjectsDidChange), name: .NSManagedObjectContextObjectsDidChange, object: fervorController.persistentContainer.viewContext)
|
||||||
|
|
||||||
} catch {
|
|
||||||
let alert = UIAlertController(title: "Error fetching items", message: error.localizedDescription, preferredStyle: .alert)
|
|
||||||
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
|
|
||||||
present(alert, animated: true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, NSManagedObjectID> {
|
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, NSManagedObjectID> {
|
||||||
|
@ -104,73 +92,59 @@ class ItemsViewController: UIViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func fetchItems() {
|
||||||
|
do {
|
||||||
|
let ids = try fervorController.persistentContainer.viewContext.fetch(fetchRequest)
|
||||||
|
|
||||||
|
var snapshot = NSDiffableDataSourceSnapshot<Section, NSManagedObjectID>()
|
||||||
|
snapshot.appendSections([.items])
|
||||||
|
snapshot.appendItems(ids, toSection: .items)
|
||||||
|
dataSource.apply(snapshot, animatingDifferences: false)
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
let alert = UIAlertController(title: "Error fetching items", message: error.localizedDescription, preferredStyle: .alert)
|
||||||
|
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
|
||||||
|
present(alert, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var updateId = 0
|
||||||
|
|
||||||
@objc private func managedObjectsDidChange(_ notification: Notification) {
|
@objc private func managedObjectsDidChange(_ notification: Notification) {
|
||||||
|
let id = updateId
|
||||||
|
updateId += 1
|
||||||
|
print("\(id) Managed objects did change")
|
||||||
let inserted = notification.userInfo?[NSInsertedObjectsKey] as? NSSet
|
let inserted = notification.userInfo?[NSInsertedObjectsKey] as? NSSet
|
||||||
let updated = notification.userInfo?[NSUpdatedObjectsKey] as? NSSet
|
let updated = notification.userInfo?[NSUpdatedObjectsKey] as? NSSet
|
||||||
let deleted = notification.userInfo?[NSDeletedObjectsKey] as? NSSet
|
let deleted = notification.userInfo?[NSDeletedObjectsKey] as? NSSet
|
||||||
|
|
||||||
// managed objectss from the notification are tied to the thread it was delivered on
|
let hasInsertedItems = inserted?.lazy.compactMap { $0 as? Item }.contains { fetchRequest.predicate?.evaluate(with: $0) ?? true } ?? false
|
||||||
// so get the published dates and evaluate the predicate here
|
|
||||||
let insertedItems = inserted?.compactMap { $0 as? Item }.filter { fetchRequest.predicate?.evaluate(with: $0) ?? true }.map { ($0, $0.published) }
|
if hasInsertedItems {
|
||||||
|
print("\(id) Has inserted items, skipping merge")
|
||||||
|
// if any items were inserted, just refetch everything. it's more expensive than it's worth to try and merge the changes into the current snapshot
|
||||||
|
self.fetchItems()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var snapshot = self.dataSource.snapshot()
|
var snapshot = self.dataSource.snapshot()
|
||||||
|
// the itemIdentifiers getter takes a lot of time in profiles, so only call the getter once
|
||||||
|
let snapshotItems = snapshot.itemIdentifiers
|
||||||
|
|
||||||
if let updated = updated {
|
if let updated = updated {
|
||||||
let knownUpdated = updated.compactMap { ($0 as? Item)?.objectID }.filter { snapshot.itemIdentifiers.contains($0) }
|
print("\(id) Updated: \(updated.count)")
|
||||||
|
let knownUpdated = updated.compactMap { ($0 as? Item)?.objectID }.filter { snapshotItems.contains($0) }
|
||||||
snapshot.reconfigureItems(knownUpdated)
|
snapshot.reconfigureItems(knownUpdated)
|
||||||
}
|
}
|
||||||
if let deleted = deleted {
|
if let deleted = deleted {
|
||||||
let knownDeleted = deleted.compactMap { ($0 as? Item)?.objectID }.filter { snapshot.itemIdentifiers.contains($0) }
|
print("\(id) Deleted: \(deleted.count)")
|
||||||
|
let knownDeleted = deleted.compactMap { ($0 as? Item)?.objectID }.filter { snapshotItems.contains($0) }
|
||||||
snapshot.deleteItems(knownDeleted)
|
snapshot.deleteItems(knownDeleted)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let insertedItems = insertedItems {
|
print("\(id) Applying snapshot")
|
||||||
self.fervorController.persistentContainer.performBackgroundTask { ctx in
|
|
||||||
|
|
||||||
// for newly inserted items, the ctx doesn't have the published date so we check the data we got from the notification
|
|
||||||
func publishedForItem(_ id: NSManagedObjectID) -> Date? {
|
|
||||||
if let (_, pub) = insertedItems.first(where: { $0.0.objectID == id }) {
|
|
||||||
return pub
|
|
||||||
} else {
|
|
||||||
return (ctx.object(with: id) as! Item).published
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: this feels inefficient
|
|
||||||
for (inserted, insertedPublished) in insertedItems {
|
|
||||||
// todo: uhh, what does sql do if the published date is nil?
|
|
||||||
guard let insertedPublished = insertedPublished else {
|
|
||||||
snapshot.insertItems([inserted.objectID], beforeItem: snapshot.itemIdentifiers.first!)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = 0
|
|
||||||
while index < snapshot.itemIdentifiers.count,
|
|
||||||
let pub = publishedForItem(snapshot.itemIdentifiers[index]),
|
|
||||||
insertedPublished < pub {
|
|
||||||
index += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// index is the index of the first item which the inserted one was published after
|
|
||||||
// (i.e., the item that should appear immediately after inserted in the list)
|
|
||||||
|
|
||||||
if index == snapshot.itemIdentifiers.count {
|
|
||||||
snapshot.appendItems([inserted.objectID], toSection: .items)
|
|
||||||
} else {
|
|
||||||
snapshot.insertItems([inserted.objectID], beforeItem: snapshot.itemIdentifiers[index])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.dataSource.apply(snapshot, animatingDifferences: false)
|
self.dataSource.apply(snapshot, animatingDifferences: false)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.dataSource.apply(snapshot, animatingDifferences: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// Created by Shadowfacts on 11/25/21.
|
// Created by Shadowfacts on 11/25/21.
|
||||||
//
|
//
|
||||||
|
|
||||||
@preconcurrency import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import AuthenticationServices
|
import AuthenticationServices
|
||||||
import Fervor
|
import Fervor
|
||||||
|
|
|
@ -70,6 +70,9 @@ class ReadViewController: UIViewController {
|
||||||
// transparent background required to prevent white flash in dark mode, just using .appBackground doesn't work
|
// transparent background required to prevent white flash in dark mode, just using .appBackground doesn't work
|
||||||
webView.isOpaque = false
|
webView.isOpaque = false
|
||||||
webView.backgroundColor = .clear
|
webView.backgroundColor = .clear
|
||||||
|
if #available(iOS 16.0, *) {
|
||||||
|
webView.isFindInteractionEnabled = true
|
||||||
|
}
|
||||||
if let content = itemContentHTML() {
|
if let content = itemContentHTML() {
|
||||||
webView.loadHTMLString(content, baseURL: item.url)
|
webView.loadHTMLString(content, baseURL: item.url)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue