Use CoreData for notifications screen

This commit is contained in:
Shadowfacts 2020-04-27 19:20:09 -04:00
parent fa1daa682f
commit f53474ac90
Signed by untrusted user: shadowfacts
GPG Key ID: 94A5AB95422746E5
9 changed files with 46 additions and 43 deletions

View File

@ -46,14 +46,14 @@ public class Status: Decodable {
return Request<Card>(method: .get, path: "/api/v1/statuses/\(status.id)/card") return Request<Card>(method: .get, path: "/api/v1/statuses/\(status.id)/card")
} }
public static func getFavourites(_ status: Status, range: RequestRange = .default) -> Request<[Account]> { public static func getFavourites(_ statusID: String, range: RequestRange = .default) -> Request<[Account]> {
var request = Request<[Account]>(method: .get, path: "/api/v1/statuses/\(status.id)/favourited_by") var request = Request<[Account]>(method: .get, path: "/api/v1/statuses/\(statusID)/favourited_by")
request.range = range request.range = range
return request return request
} }
public static func getReblogs(_ status: Status, range: RequestRange = .default) -> Request<[Account]> { public static func getReblogs(_ statusID: String, range: RequestRange = .default) -> Request<[Account]> {
var request = Request<[Account]>(method: .get, path: "/api/v1/statuses/\(status.id)/reblogged_by") var request = Request<[Account]>(method: .get, path: "/api/v1/statuses/\(statusID)/reblogged_by")
request.range = range request.range = range
return request return request
} }

View File

@ -9,14 +9,14 @@
import Foundation import Foundation
public class NotificationGroup { public class NotificationGroup {
public let notificationIDs: [String] public let notifications: [Notification]
public let id: String public let id: String
public let kind: Notification.Kind public let kind: Notification.Kind
public let statusState: StatusState? public let statusState: StatusState?
init?(notifications: [Notification]) { init?(notifications: [Notification]) {
guard !notifications.isEmpty else { return nil } guard !notifications.isEmpty else { return nil }
self.notificationIDs = notifications.map { $0.id } self.notifications = notifications
self.id = notifications.first!.id self.id = notifications.first!.id
self.kind = notifications.first!.kind self.kind = notifications.first!.kind
if kind == .mention { if kind == .mention {

View File

@ -92,6 +92,12 @@
ReferencedContainer = "container:Tusker.xcodeproj"> ReferencedContainer = "container:Tusker.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "-com.apple.CoreData.ConcurrencyDebug 1"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Release" buildConfiguration = "Release"

View File

@ -84,7 +84,7 @@ class MastodonController {
run(request) { response in run(request) { response in
guard case let .success(account, _) = response else { fatalError() } guard case let .success(account, _) = response else { fatalError() }
self.account = account self.account = account
self.cache.add(account: account) self.persistentContainer.addOrUpdate(account: account)
completion?(account) completion?(account)
} }
} }

View File

@ -51,7 +51,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
} }
} }
func addOrUpdate(status: Status, incrementReferenceCount: Bool, completion: (() -> Void)?) { func addOrUpdate(status: Status, incrementReferenceCount: Bool, completion: (() -> Void)? = nil) {
backgroundContext.perform { backgroundContext.perform {
self.upsert(status: status, incrementReferenceCount: incrementReferenceCount) self.upsert(status: status, incrementReferenceCount: incrementReferenceCount)
if self.backgroundContext.hasChanges { if self.backgroundContext.hasChanges {
@ -91,7 +91,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
} }
} }
func addOrUpdate(account: Account, completion: (() -> Void)?) { func addOrUpdate(account: Account, completion: (() -> Void)? = nil) {
backgroundContext.perform { backgroundContext.perform {
self.upsert(account: account) self.upsert(account: account)
if self.backgroundContext.hasChanges { if self.backgroundContext.hasChanges {

View File

@ -63,9 +63,8 @@ class NotificationsTableViewController: EnhancedTableViewController {
self.groups.append(contentsOf: groups) self.groups.append(contentsOf: groups)
self.mastodonController.cache.addAll(notifications: notifications) self.mastodonController.persistentContainer.addAll(statuses: notifications.compactMap { $0.status })
self.mastodonController.cache.addAll(statuses: notifications.compactMap { $0.status }) self.mastodonController.persistentContainer.addAll(accounts: notifications.map { $0.account })
self.mastodonController.cache.addAll(accounts: notifications.map { $0.account })
self.newer = pagination?.newer self.newer = pagination?.newer
self.older = pagination?.older self.older = pagination?.older
@ -92,7 +91,7 @@ class NotificationsTableViewController: EnhancedTableViewController {
switch group.kind { switch group.kind {
case .mention: case .mention:
guard let notification = mastodonController.cache.notification(for: group.notificationIDs.first!), guard let notification = group.notifications.first,
let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as? TimelineStatusTableViewCell else { let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as? TimelineStatusTableViewCell else {
fatalError() fatalError()
} }
@ -113,7 +112,7 @@ class NotificationsTableViewController: EnhancedTableViewController {
return cell return cell
case .followRequest: case .followRequest:
guard let notification = mastodonController.cache.notification(for: group.notificationIDs.first!), guard let notification = group.notifications.first,
let cell = tableView.dequeueReusableCell(withIdentifier: followRequestCell, for: indexPath) as? FollowRequestNotificationTableViewCell else { fatalError() } let cell = tableView.dequeueReusableCell(withIdentifier: followRequestCell, for: indexPath) as? FollowRequestNotificationTableViewCell else { fatalError() }
cell.delegate = self cell.delegate = self
cell.updateUI(notification: notification) cell.updateUI(notification: notification)
@ -195,8 +194,8 @@ class NotificationsTableViewController: EnhancedTableViewController {
func dismissNotificationsInGroup(at indexPath: IndexPath, completion: (() -> Void)? = nil) { func dismissNotificationsInGroup(at indexPath: IndexPath, completion: (() -> Void)? = nil) {
let group = DispatchGroup() let group = DispatchGroup()
groups[indexPath.row].notificationIDs groups[indexPath.row].notifications
.map(Pachyderm.Notification.dismiss(id:)) .map { Pachyderm.Notification.dismiss(id: $0.id) }
.forEach { (request) in .forEach { (request) in
group.enter() group.enter()
mastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
@ -259,8 +258,8 @@ extension NotificationsTableViewController: StatusTableViewCellDelegate {
extension NotificationsTableViewController: UITableViewDataSourcePrefetching { extension NotificationsTableViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) { func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths { for indexPath in indexPaths {
for notificationID in groups[indexPath.row].notificationIDs { for notification in groups[indexPath.row].notifications {
guard let notification = mastodonController.cache.notification(for: notificationID) else { continue } // todo: this account object could be stale
_ = ImageCache.avatars.get(notification.account.avatar, completion: nil) _ = ImageCache.avatars.get(notification.account.avatar, completion: nil)
} }
} }
@ -268,8 +267,7 @@ extension NotificationsTableViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) { func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths { for indexPath in indexPaths {
for notificationID in groups[indexPath.row].notificationIDs { for notification in groups[indexPath.row].notifications {
guard let notification = mastodonController.cache.notification(for: notificationID) else { continue }
ImageCache.avatars.cancelWithoutCallback(notification.account.avatar) ImageCache.avatars.cancelWithoutCallback(notification.account.avatar)
} }
} }

View File

@ -73,13 +73,13 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
if accountIDs == nil { if accountIDs == nil {
// account IDs haven't been set, so perform a request to load them // account IDs haven't been set, so perform a request to load them
guard let status = mastodonController.cache.status(for: statusID) else { guard let status = mastodonController.persistentContainer.status(for: statusID) else {
fatalError("Missing cached status \(statusID)") fatalError("Missing cached status \(statusID)")
} }
tableView.tableFooterView = UIActivityIndicatorView(style: .large) tableView.tableFooterView = UIActivityIndicatorView(style: .large)
let request = actionType == .favorite ? Status.getFavourites(status) : Status.getReblogs(status) let request = actionType == .favorite ? Status.getFavourites(status.id) : Status.getReblogs(status.id)
mastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
guard case let .success(accounts, _) = response else { fatalError() } guard case let .success(accounts, _) = response else { fatalError() }
self.mastodonController.cache.addAll(accounts: accounts) self.mastodonController.cache.addAll(accounts: accounts)

View File

@ -38,7 +38,8 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
} }
@objc func updateUIForPreferences() { @objc func updateUIForPreferences() {
let people = group.notificationIDs.compactMap(mastodonController.cache.notification(for:)).map { $0.account } // todo: is this compactMap necessary?
let people = group.notifications.compactMap { mastodonController.persistentContainer.account(for: $0.account.id) }
updateActionLabel(people: people) updateActionLabel(people: people)
for case let imageView as UIImageView in actionAvatarStackView.arrangedSubviews { for case let imageView as UIImageView in actionAvatarStackView.arrangedSubviews {
@ -52,7 +53,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
} }
self.group = group self.group = group
guard let firstNotification = mastodonController.cache.notification(for: group.notificationIDs.first!) else { fatalError() } guard let firstNotification = group.notifications.first else { fatalError() }
let status = firstNotification.status! let status = firstNotification.status!
self.statusID = status.id self.statusID = status.id
@ -67,7 +68,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
fatalError() fatalError()
} }
let people = group.notificationIDs.compactMap(mastodonController.cache.notification(for:)).map { $0.account } let people = group.notifications.compactMap { mastodonController.persistentContainer.account(for: $0.account.id) }
actionAvatarStackView.arrangedSubviews.forEach { $0.removeFromSuperview() } actionAvatarStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
for account in people { for account in people {
@ -98,8 +99,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
} }
func updateTimestamp() { func updateTimestamp() {
guard let id = group.notificationIDs.first, guard let notification = group.notifications.first else {
let notification = mastodonController.cache.notification(for: id) else {
fatalError("Missing cached notification") fatalError("Missing cached notification")
} }
@ -126,7 +126,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
} }
} }
func updateActionLabel(people: [Account]) { func updateActionLabel(people: [AccountMO]) {
let verb: String let verb: String
switch group.kind { switch group.kind {
case .favourite: case .favourite:
@ -163,7 +163,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
extension ActionNotificationGroupTableViewCell: SelectableTableViewCell { extension ActionNotificationGroupTableViewCell: SelectableTableViewCell {
func didSelectCell() { func didSelectCell() {
guard let delegate = delegate else { return } guard let delegate = delegate else { return }
let notifications = group.notificationIDs.compactMap(mastodonController.cache.notification(for:)) let notifications = group.notifications
let accountIDs = notifications.map { $0.account.id } let accountIDs = notifications.map { $0.account.id }
let action: StatusActionAccountListTableViewController.ActionType let action: StatusActionAccountListTableViewController.ActionType
switch notifications.first!.kind { switch notifications.first!.kind {
@ -184,7 +184,7 @@ extension ActionNotificationGroupTableViewCell: MenuPreviewProvider {
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? { func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
return (content: { return (content: {
let notifications = self.group.notificationIDs.compactMap(self.mastodonController.cache.notification(for:)) let notifications = self.group.notifications
let accountIDs = notifications.map { $0.account.id } let accountIDs = notifications.map { $0.account.id }
let action: StatusActionAccountListTableViewController.ActionType let action: StatusActionAccountListTableViewController.ActionType
switch notifications.first!.kind { switch notifications.first!.kind {

View File

@ -34,7 +34,7 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
} }
@objc func updateUIForPreferences() { @objc func updateUIForPreferences() {
let people = group.notificationIDs.compactMap(mastodonController.cache.notification(for:)).map { $0.account } let people = group.notifications.compactMap { mastodonController.persistentContainer.account(for: $0.account.id) }
updateActionLabel(people: people) updateActionLabel(people: people)
for case let imageView as UIImageView in avatarStackView.arrangedSubviews { for case let imageView as UIImageView in avatarStackView.arrangedSubviews {
@ -45,7 +45,7 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
func updateUI(group: NotificationGroup) { func updateUI(group: NotificationGroup) {
self.group = group self.group = group
let people = group.notificationIDs.compactMap(mastodonController.cache.notification(for:)).map { $0.account } let people = group.notifications.compactMap { mastodonController.persistentContainer.account(for: $0.account.id) }
updateActionLabel(people: people) updateActionLabel(people: people)
updateTimestamp() updateTimestamp()
@ -71,8 +71,8 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
} }
} }
func updateActionLabel(people: [Account]) { func updateActionLabel(people: [AccountMO]) {
// todo: update to use managed objects // todo: custom emoji in people display names
// todo: figure out how to localize this // todo: figure out how to localize this
let peopleStr: String let peopleStr: String
switch people.count { switch people.count {
@ -88,8 +88,7 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
} }
func updateTimestamp() { func updateTimestamp() {
guard let id = group.notificationIDs.first, guard let notification = group.notifications.first else {
let notification = mastodonController.cache.notification(for: id) else {
fatalError("Missing cached notification") fatalError("Missing cached notification")
} }
@ -128,14 +127,14 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
extension FollowNotificationGroupTableViewCell: SelectableTableViewCell { extension FollowNotificationGroupTableViewCell: SelectableTableViewCell {
func didSelectCell() { func didSelectCell() {
let people = group.notificationIDs.compactMap(mastodonController.cache.notification(for:)).map { $0.account.id } let accountIDs = group.notifications.map { $0.account.id }
switch people.count { switch accountIDs.count {
case 0: case 0:
return return
case 1: case 1:
delegate?.selected(account: people.first!) delegate?.selected(account: accountIDs.first!)
default: default:
delegate?.showFollowedByList(accountIDs: people) delegate?.showFollowedByList(accountIDs: accountIDs)
} }
} }
} }
@ -145,7 +144,7 @@ extension FollowNotificationGroupTableViewCell: MenuPreviewProvider {
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? { func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
guard let mastodonController = mastodonController else { return nil } guard let mastodonController = mastodonController else { return nil }
let accountIDs = self.group.notificationIDs.compactMap(mastodonController.cache.notification(for:)).map { $0.account.id } let accountIDs = self.group.notifications.map { $0.account.id }
return (content: { return (content: {
if accountIDs.count == 1 { if accountIDs.count == 1 {
return ProfileTableViewController(accountID: accountIDs.first!, mastodonController: mastodonController) return ProfileTableViewController(accountID: accountIDs.first!, mastodonController: mastodonController)