Fix crash when opening profile view controller with uncached account

E.g., by tapping a mention in a status
This commit is contained in:
Shadowfacts 2022-10-29 18:55:13 -04:00
parent cbbe9ec11f
commit d5433e9b91
3 changed files with 45 additions and 19 deletions

View File

@ -147,19 +147,20 @@ class MastodonCachePersistentStore: NSPersistentContainer {
}
@discardableResult
private func upsert(account: Account) -> AccountMO {
if let accountMO = self.account(for: account.id, in: self.backgroundContext) {
private func upsert(account: Account, in context: NSManagedObjectContext) -> AccountMO {
if let accountMO = self.account(for: account.id, in: context) {
accountMO.updateFrom(apiAccount: account, container: self)
return accountMO
} else {
return AccountMO(apiAccount: account, container: self, context: self.backgroundContext)
return AccountMO(apiAccount: account, container: self, context: context)
}
}
func addOrUpdate(account: Account, completion: ((AccountMO) -> Void)? = nil) {
backgroundContext.perform {
let accountMO = self.upsert(account: account)
self.save(context: self.backgroundContext)
func addOrUpdate(account: Account, in context: NSManagedObjectContext? = nil, completion: ((AccountMO) -> Void)? = nil) {
let context = context ?? backgroundContext
context.perform {
let accountMO = self.upsert(account: account, in: context)
self.save(context: context)
completion?(accountMO)
self.accountSubject.send(account.id)
}
@ -199,7 +200,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
func addAll(accounts: [Account], completion: (() -> Void)? = nil) {
backgroundContext.perform {
accounts.forEach { self.upsert(account: $0) }
accounts.forEach { self.upsert(account: $0, in: self.backgroundContext) }
self.save(context: self.backgroundContext)
completion?()
accounts.forEach { self.accountSubject.send($0.id) }
@ -213,7 +214,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
// since the status has the same account as the notification
let accounts = notifications.filter { $0.kind != .mention }.map { $0.account }
statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) }
accounts.forEach { self.upsert(account: $0) }
accounts.forEach { self.upsert(account: $0, in: self.backgroundContext) }
self.save(context: self.backgroundContext)
completion?()
statuses.forEach { self.statusSubject.send($0.id) }
@ -227,7 +228,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
var updatedStatuses = [String]()
block(self.backgroundContext, { (accounts) in
accounts.forEach { self.upsert(account: $0) }
accounts.forEach { self.upsert(account: $0, in: self.backgroundContext) }
updatedAccounts.append(contentsOf: accounts.map { $0.id })
}, { (statuses) in
statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) }

View File

@ -29,9 +29,10 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
view as! UICollectionView
}
private(set) var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
private(set) var headerCell: ProfileHeaderCollectionViewCell?
private var state: State = .unloaded
init(accountID: String?, kind: Kind, owner: ProfileViewController) {
self.accountID = accountID
self.kind = kind
@ -92,9 +93,18 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
.receive(on: DispatchQueue.main)
.filter { [unowned self] in $0 == self.accountID }
.sink { [unowned self] id in
var snapshot = dataSource.snapshot()
snapshot.reconfigureItems([.header(id)])
dataSource.apply(snapshot, animatingDifferences: true)
switch state {
case .unloaded:
Task {
await load()
}
case .loading:
break
case .loaded, .addedHeader:
var snapshot = dataSource.snapshot()
snapshot.reconfigureItems([.header(id)])
dataSource.apply(snapshot, animatingDifferences: true)
}
}
.store(in: &cancellables)
}
@ -159,20 +169,26 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
}
private func load() async {
guard accountID != nil,
await controller.state == .notLoadedInitial,
isViewLoaded else {
guard isViewLoaded,
let accountID,
case .unloaded = state,
mastodonController.persistentContainer.account(for: accountID) != nil else {
return
}
state = .loading
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.header, .pinned, .statuses])
snapshot.appendItems([.header(accountID)], toSection: .header)
await apply(snapshot, animatingDifferences: false)
print("added header item")
state = .addedHeader
await controller.loadInitial()
await tryLoadPinned()
state = .loaded
}
private func tryLoadPinned() async {
@ -222,6 +238,15 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
}
extension ProfileStatusesViewController {
enum State {
case unloaded
case loading
case addedHeader
case loaded
}
}
extension ProfileStatusesViewController {
enum Kind {
case statuses, withReplies, onlyMedia

View File

@ -116,7 +116,7 @@ class ProfileViewController: UIPageViewController {
let req = Client.getAccount(id: accountID)
let (account, _) = try await mastodonController.run(req)
let mo = await withCheckedContinuation { continuation in
mastodonController.persistentContainer.addOrUpdate(account: account) { (mo) in
mastodonController.persistentContainer.addOrUpdate(account: account, in: mastodonController.persistentContainer.viewContext) { (mo) in
continuation.resume(returning: mo)
}
}