Compare commits
5 Commits
bce0f8ef18
...
a703b7cc0a
Author | SHA1 | Date |
---|---|---|
Shadowfacts | a703b7cc0a | |
Shadowfacts | e78bec8409 | |
Shadowfacts | 412e4a4dc5 | |
Shadowfacts | 81e10326d3 | |
Shadowfacts | 20f88ef161 |
|
@ -10,6 +10,9 @@ import Foundation
|
||||||
import CoreData
|
import CoreData
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import Combine
|
import Combine
|
||||||
|
import OSLog
|
||||||
|
|
||||||
|
fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PersistentStore")
|
||||||
|
|
||||||
class MastodonCachePersistentStore: NSPersistentContainer {
|
class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
|
|
||||||
|
@ -49,7 +52,8 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
|
|
||||||
loadPersistentStores { (description, error) in
|
loadPersistentStores { (description, error) in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
fatalError("Unable to load persistent store: \(error)")
|
logger.error("Unable to load persistent store: \(String(describing: error), privacy: .public)")
|
||||||
|
fatalError("Unable to load persistent store")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +62,18 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(managedObjectsDidChange), name: .NSManagedObjectContextObjectsDidChange, object: viewContext)
|
NotificationCenter.default.addObserver(self, selector: #selector(managedObjectsDidChange), name: .NSManagedObjectContextObjectsDidChange, object: viewContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func save(context: NSManagedObjectContext) {
|
||||||
|
guard context.hasChanges else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
try context.save()
|
||||||
|
} catch {
|
||||||
|
logger.error("Unable to save managed object context: \(String(describing: error), privacy: .public)")
|
||||||
|
fatalError("Unable to save managed object context")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func status(for id: String, in context: NSManagedObjectContext? = nil) -> StatusMO? {
|
func status(for id: String, in context: NSManagedObjectContext? = nil) -> StatusMO? {
|
||||||
let context = context ?? viewContext
|
let context = context ?? viewContext
|
||||||
let request: NSFetchRequest<StatusMO> = StatusMO.fetchRequest()
|
let request: NSFetchRequest<StatusMO> = StatusMO.fetchRequest()
|
||||||
|
@ -84,9 +100,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
let context = context ?? backgroundContext
|
let context = context ?? backgroundContext
|
||||||
context.perform {
|
context.perform {
|
||||||
let statusMO = self.upsert(status: status, context: context)
|
let statusMO = self.upsert(status: status, context: context)
|
||||||
if context.hasChanges {
|
self.save(context: context)
|
||||||
try! context.save()
|
|
||||||
}
|
|
||||||
completion?(statusMO)
|
completion?(statusMO)
|
||||||
self.statusSubject.send(status.id)
|
self.statusSubject.send(status.id)
|
||||||
}
|
}
|
||||||
|
@ -95,9 +109,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
@MainActor
|
@MainActor
|
||||||
func addOrUpdateOnViewContext(status: Status) -> StatusMO {
|
func addOrUpdateOnViewContext(status: Status) -> StatusMO {
|
||||||
let statusMO = self.upsert(status: status, context: viewContext)
|
let statusMO = self.upsert(status: status, context: viewContext)
|
||||||
if viewContext.hasChanges {
|
self.save(context: viewContext)
|
||||||
try! viewContext.save()
|
|
||||||
}
|
|
||||||
statusSubject.send(status.id)
|
statusSubject.send(status.id)
|
||||||
return statusMO
|
return statusMO
|
||||||
}
|
}
|
||||||
|
@ -105,9 +117,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
func addAll(statuses: [Status], completion: (() -> Void)? = nil) {
|
func addAll(statuses: [Status], completion: (() -> Void)? = nil) {
|
||||||
backgroundContext.perform {
|
backgroundContext.perform {
|
||||||
statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) }
|
statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) }
|
||||||
if self.backgroundContext.hasChanges {
|
self.save(context: self.backgroundContext)
|
||||||
try! self.backgroundContext.save()
|
|
||||||
}
|
|
||||||
statuses.forEach { self.statusSubject.send($0.id) }
|
statuses.forEach { self.statusSubject.send($0.id) }
|
||||||
completion?()
|
completion?()
|
||||||
}
|
}
|
||||||
|
@ -146,9 +156,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
func addOrUpdate(account: Account, completion: ((AccountMO) -> Void)? = nil) {
|
func addOrUpdate(account: Account, completion: ((AccountMO) -> Void)? = nil) {
|
||||||
backgroundContext.perform {
|
backgroundContext.perform {
|
||||||
let accountMO = self.upsert(account: account)
|
let accountMO = self.upsert(account: account)
|
||||||
if self.backgroundContext.hasChanges {
|
self.save(context: self.backgroundContext)
|
||||||
try! self.backgroundContext.save()
|
|
||||||
}
|
|
||||||
completion?(accountMO)
|
completion?(accountMO)
|
||||||
self.accountSubject.send(account.id)
|
self.accountSubject.send(account.id)
|
||||||
}
|
}
|
||||||
|
@ -180,9 +188,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
func addOrUpdate(relationship: Relationship, completion: ((RelationshipMO) -> Void)? = nil) {
|
func addOrUpdate(relationship: Relationship, completion: ((RelationshipMO) -> Void)? = nil) {
|
||||||
backgroundContext.perform {
|
backgroundContext.perform {
|
||||||
let relationshipMO = self.upsert(relationship: relationship)
|
let relationshipMO = self.upsert(relationship: relationship)
|
||||||
if self.backgroundContext.hasChanges {
|
self.save(context: self.backgroundContext)
|
||||||
try! self.backgroundContext.save()
|
|
||||||
}
|
|
||||||
completion?(relationshipMO)
|
completion?(relationshipMO)
|
||||||
self.relationshipSubject.send(relationship.id)
|
self.relationshipSubject.send(relationship.id)
|
||||||
}
|
}
|
||||||
|
@ -191,9 +197,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
func addAll(accounts: [Account], completion: (() -> Void)? = nil) {
|
func addAll(accounts: [Account], completion: (() -> Void)? = nil) {
|
||||||
backgroundContext.perform {
|
backgroundContext.perform {
|
||||||
accounts.forEach { self.upsert(account: $0) }
|
accounts.forEach { self.upsert(account: $0) }
|
||||||
if self.backgroundContext.hasChanges {
|
self.save(context: self.backgroundContext)
|
||||||
try! self.backgroundContext.save()
|
|
||||||
}
|
|
||||||
completion?()
|
completion?()
|
||||||
accounts.forEach { self.accountSubject.send($0.id) }
|
accounts.forEach { self.accountSubject.send($0.id) }
|
||||||
}
|
}
|
||||||
|
@ -207,9 +211,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
let accounts = notifications.filter { $0.kind != .mention }.map { $0.account }
|
let accounts = notifications.filter { $0.kind != .mention }.map { $0.account }
|
||||||
statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) }
|
statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) }
|
||||||
accounts.forEach { self.upsert(account: $0) }
|
accounts.forEach { self.upsert(account: $0) }
|
||||||
if self.backgroundContext.hasChanges {
|
self.save(context: self.backgroundContext)
|
||||||
try! self.backgroundContext.save()
|
|
||||||
}
|
|
||||||
completion?()
|
completion?()
|
||||||
statuses.forEach { self.statusSubject.send($0.id) }
|
statuses.forEach { self.statusSubject.send($0.id) }
|
||||||
accounts.forEach { self.accountSubject.send($0.id) }
|
accounts.forEach { self.accountSubject.send($0.id) }
|
||||||
|
@ -232,9 +234,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
updatedAccounts.forEach(self.accountSubject.send)
|
updatedAccounts.forEach(self.accountSubject.send)
|
||||||
updatedStatuses.forEach(self.statusSubject.send)
|
updatedStatuses.forEach(self.statusSubject.send)
|
||||||
|
|
||||||
if self.backgroundContext.hasChanges {
|
self.save(context: self.backgroundContext)
|
||||||
try! self.backgroundContext.save()
|
|
||||||
}
|
|
||||||
completion?()
|
completion?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>OSLogPreferences</key>
|
<key>OSLogPreferences</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>$(PRODUCT_BUNDLE_IDENTIFIER)</key>
|
<key>space.vaccor.Tusker</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>DEFAULT-OPTIONS</key>
|
<key>DEFAULT-OPTIONS</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
|
|
@ -58,7 +58,7 @@ class HashtagTimelineViewController: TimelineTableViewController {
|
||||||
} else {
|
} else {
|
||||||
_ = SavedHashtag(hashtag: hashtag, context: context)
|
_ = SavedHashtag(hashtag: hashtag, context: context)
|
||||||
}
|
}
|
||||||
try! context.save()
|
mastodonController.persistentContainer.save(context: context)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,7 @@ class InstanceTimelineViewController: TimelineTableViewController {
|
||||||
_ = SavedInstance(url: instanceURL, context: context)
|
_ = SavedInstance(url: instanceURL, context: context)
|
||||||
delegate?.didSaveInstance(url: instanceURL)
|
delegate?.didSaveInstance(url: instanceURL)
|
||||||
}
|
}
|
||||||
try? context.save()
|
mastodonController.persistentContainer.save(context: context)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,7 +124,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
private func applyInitialSnapshot() {
|
private func applyInitialSnapshot() {
|
||||||
if case .public(let local) = timeline,
|
if case .public(let local) = timeline,
|
||||||
(local && !Preferences.shared.hasShownLocalTimelineDescription) ||
|
(local && !Preferences.shared.hasShownLocalTimelineDescription) ||
|
||||||
(!local && Preferences.shared.hasShownFederatedTimelineDescription) {
|
(!local && !Preferences.shared.hasShownFederatedTimelineDescription) {
|
||||||
var snapshot = dataSource.snapshot()
|
var snapshot = dataSource.snapshot()
|
||||||
snapshot.appendSections([.header])
|
snapshot.appendSections([.header])
|
||||||
snapshot.appendItems([.publicTimelineDescription], toSection: .header)
|
snapshot.appendItems([.publicTimelineDescription], toSection: .header)
|
||||||
|
@ -144,6 +144,25 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
|
if isShowingTimelineDescription,
|
||||||
|
case .public(let local) = timeline {
|
||||||
|
if local {
|
||||||
|
Preferences.shared.hasShownLocalTimelineDescription = true
|
||||||
|
} else {
|
||||||
|
Preferences.shared.hasShownFederatedTimelineDescription = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidDisappear(_ animated: Bool) {
|
||||||
|
super.viewDidDisappear(animated)
|
||||||
|
|
||||||
|
pruneOffscreenRows()
|
||||||
|
}
|
||||||
|
|
||||||
private func removeTimelineDescriptionCell() {
|
private func removeTimelineDescriptionCell() {
|
||||||
var snapshot = dataSource.snapshot()
|
var snapshot = dataSource.snapshot()
|
||||||
snapshot.deleteSections([.header])
|
snapshot.deleteSections([.header])
|
||||||
|
@ -151,6 +170,25 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
isShowingTimelineDescription = false
|
isShowingTimelineDescription = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func pruneOffscreenRows() {
|
||||||
|
guard let lastVisibleIndexPath = collectionView.indexPathsForVisibleItems.last else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var snapshot = dataSource.snapshot()
|
||||||
|
let items = snapshot.itemIdentifiers(inSection: .statuses)
|
||||||
|
let pageSize = 20
|
||||||
|
let numberOfPagesToPrune = (items.count - lastVisibleIndexPath.row - 1) / pageSize
|
||||||
|
if numberOfPagesToPrune > 0 {
|
||||||
|
let itemsToRemove = Array(items.suffix(numberOfPagesToPrune * pageSize))
|
||||||
|
snapshot.deleteItems(itemsToRemove)
|
||||||
|
}
|
||||||
|
dataSource.apply(snapshot, animatingDifferences: false)
|
||||||
|
|
||||||
|
if case .status(id: let id, state: _) = snapshot.itemIdentifiers(inSection: .statuses).last {
|
||||||
|
older = .before(id: id, count: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc func refresh() {
|
@objc func refresh() {
|
||||||
Task {
|
Task {
|
||||||
await controller.loadNewer()
|
await controller.loadNewer()
|
||||||
|
|
|
@ -110,7 +110,7 @@ extension MenuActionProvider {
|
||||||
} else {
|
} else {
|
||||||
_ = SavedHashtag(hashtag: hashtag, context: context)
|
_ = SavedHashtag(hashtag: hashtag, context: context)
|
||||||
}
|
}
|
||||||
try! context.save()
|
mastodonController.persistentContainer.save(context: context)
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -33,9 +33,17 @@ class AttachmentsContainerView: UIView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override init(frame: CGRect) {
|
||||||
super.awakeFromNib()
|
super.init(frame: frame)
|
||||||
|
commonInit()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
commonInit()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func commonInit() {
|
||||||
self.isUserInteractionEnabled = true
|
self.isUserInteractionEnabled = true
|
||||||
self.layer.cornerRadius = 5
|
self.layer.cornerRadius = 5
|
||||||
self.layer.masksToBounds = true
|
self.layer.masksToBounds = true
|
||||||
|
|
|
@ -170,9 +170,7 @@ class StatusPollView: UIView {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
status.poll = poll
|
status.poll = poll
|
||||||
if container.viewContext.hasChanges {
|
container.save(context: container.viewContext)
|
||||||
try! container.viewContext.save()
|
|
||||||
}
|
|
||||||
container.statusSubject.send(status.id)
|
container.statusSubject.send(status.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,13 +85,12 @@ extension StatusCollectionViewCell {
|
||||||
|
|
||||||
contentContainer.contentTextView.setTextFrom(status: status)
|
contentContainer.contentTextView.setTextFrom(status: status)
|
||||||
contentContainer.attachmentsView.delegate = self
|
contentContainer.attachmentsView.delegate = self
|
||||||
|
contentContainer.attachmentsView.updateUI(status: status)
|
||||||
contentContainer.cardView.updateUI(status: status)
|
contentContainer.cardView.updateUI(status: status)
|
||||||
contentContainer.cardView.isHidden = status.card == nil
|
contentContainer.cardView.isHidden = status.card == nil
|
||||||
contentContainer.cardView.navigationDelegate = delegate
|
contentContainer.cardView.navigationDelegate = delegate
|
||||||
contentContainer.cardView.actionProvider = delegate
|
contentContainer.cardView.actionProvider = delegate
|
||||||
|
|
||||||
contentContainer.attachmentsView.updateUI(status: status)
|
|
||||||
|
|
||||||
updateStatusState(status: status)
|
updateStatusState(status: status)
|
||||||
|
|
||||||
contentWarningLabel.text = status.spoilerText
|
contentWarningLabel.text = status.spoilerText
|
||||||
|
|
Loading…
Reference in New Issue