Fix crash when opening profile view controller with uncached account
E.g., by tapping a mention in a status
This commit is contained in:
parent
cbbe9ec11f
commit
d5433e9b91
|
@ -147,19 +147,20 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
private func upsert(account: Account) -> AccountMO {
|
private func upsert(account: Account, in context: NSManagedObjectContext) -> AccountMO {
|
||||||
if let accountMO = self.account(for: account.id, in: self.backgroundContext) {
|
if let accountMO = self.account(for: account.id, in: context) {
|
||||||
accountMO.updateFrom(apiAccount: account, container: self)
|
accountMO.updateFrom(apiAccount: account, container: self)
|
||||||
return accountMO
|
return accountMO
|
||||||
} else {
|
} 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) {
|
func addOrUpdate(account: Account, in context: NSManagedObjectContext? = nil, completion: ((AccountMO) -> Void)? = nil) {
|
||||||
backgroundContext.perform {
|
let context = context ?? backgroundContext
|
||||||
let accountMO = self.upsert(account: account)
|
context.perform {
|
||||||
self.save(context: self.backgroundContext)
|
let accountMO = self.upsert(account: account, in: context)
|
||||||
|
self.save(context: context)
|
||||||
completion?(accountMO)
|
completion?(accountMO)
|
||||||
self.accountSubject.send(account.id)
|
self.accountSubject.send(account.id)
|
||||||
}
|
}
|
||||||
|
@ -199,7 +200,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, in: self.backgroundContext) }
|
||||||
self.save(context: self.backgroundContext)
|
self.save(context: self.backgroundContext)
|
||||||
completion?()
|
completion?()
|
||||||
accounts.forEach { self.accountSubject.send($0.id) }
|
accounts.forEach { self.accountSubject.send($0.id) }
|
||||||
|
@ -213,7 +214,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
// since the status has the same account as the notification
|
// since the status has the same account as the notification
|
||||||
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, in: self.backgroundContext) }
|
||||||
self.save(context: self.backgroundContext)
|
self.save(context: self.backgroundContext)
|
||||||
completion?()
|
completion?()
|
||||||
statuses.forEach { self.statusSubject.send($0.id) }
|
statuses.forEach { self.statusSubject.send($0.id) }
|
||||||
|
@ -227,7 +228,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
var updatedStatuses = [String]()
|
var updatedStatuses = [String]()
|
||||||
|
|
||||||
block(self.backgroundContext, { (accounts) in
|
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 })
|
updatedAccounts.append(contentsOf: accounts.map { $0.id })
|
||||||
}, { (statuses) in
|
}, { (statuses) in
|
||||||
statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) }
|
statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) }
|
||||||
|
|
|
@ -29,9 +29,10 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
||||||
view as! UICollectionView
|
view as! UICollectionView
|
||||||
}
|
}
|
||||||
private(set) var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
private(set) var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
|
||||||
|
|
||||||
private(set) var headerCell: ProfileHeaderCollectionViewCell?
|
private(set) var headerCell: ProfileHeaderCollectionViewCell?
|
||||||
|
|
||||||
|
private var state: State = .unloaded
|
||||||
|
|
||||||
init(accountID: String?, kind: Kind, owner: ProfileViewController) {
|
init(accountID: String?, kind: Kind, owner: ProfileViewController) {
|
||||||
self.accountID = accountID
|
self.accountID = accountID
|
||||||
self.kind = kind
|
self.kind = kind
|
||||||
|
@ -92,10 +93,19 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.filter { [unowned self] in $0 == self.accountID }
|
.filter { [unowned self] in $0 == self.accountID }
|
||||||
.sink { [unowned self] id in
|
.sink { [unowned self] id in
|
||||||
|
switch state {
|
||||||
|
case .unloaded:
|
||||||
|
Task {
|
||||||
|
await load()
|
||||||
|
}
|
||||||
|
case .loading:
|
||||||
|
break
|
||||||
|
case .loaded, .addedHeader:
|
||||||
var snapshot = dataSource.snapshot()
|
var snapshot = dataSource.snapshot()
|
||||||
snapshot.reconfigureItems([.header(id)])
|
snapshot.reconfigureItems([.header(id)])
|
||||||
dataSource.apply(snapshot, animatingDifferences: true)
|
dataSource.apply(snapshot, animatingDifferences: true)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,20 +169,26 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
||||||
}
|
}
|
||||||
|
|
||||||
private func load() async {
|
private func load() async {
|
||||||
guard accountID != nil,
|
guard isViewLoaded,
|
||||||
await controller.state == .notLoadedInitial,
|
let accountID,
|
||||||
isViewLoaded else {
|
case .unloaded = state,
|
||||||
|
mastodonController.persistentContainer.account(for: accountID) != nil else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state = .loading
|
||||||
|
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||||
snapshot.appendSections([.header, .pinned, .statuses])
|
snapshot.appendSections([.header, .pinned, .statuses])
|
||||||
snapshot.appendItems([.header(accountID)], toSection: .header)
|
snapshot.appendItems([.header(accountID)], toSection: .header)
|
||||||
await apply(snapshot, animatingDifferences: false)
|
await apply(snapshot, animatingDifferences: false)
|
||||||
print("added header item")
|
|
||||||
|
state = .addedHeader
|
||||||
|
|
||||||
await controller.loadInitial()
|
await controller.loadInitial()
|
||||||
await tryLoadPinned()
|
await tryLoadPinned()
|
||||||
|
|
||||||
|
state = .loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
private func tryLoadPinned() async {
|
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 {
|
extension ProfileStatusesViewController {
|
||||||
enum Kind {
|
enum Kind {
|
||||||
case statuses, withReplies, onlyMedia
|
case statuses, withReplies, onlyMedia
|
||||||
|
|
|
@ -116,7 +116,7 @@ class ProfileViewController: UIPageViewController {
|
||||||
let req = Client.getAccount(id: accountID)
|
let req = Client.getAccount(id: accountID)
|
||||||
let (account, _) = try await mastodonController.run(req)
|
let (account, _) = try await mastodonController.run(req)
|
||||||
let mo = await withCheckedContinuation { continuation in
|
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)
|
continuation.resume(returning: mo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue