diff --git a/Tusker/Screens/Bookmarks/BookmarksViewController.swift b/Tusker/Screens/Bookmarks/BookmarksViewController.swift index 30a7cd77..76e8c06c 100644 --- a/Tusker/Screens/Bookmarks/BookmarksViewController.swift +++ b/Tusker/Screens/Bookmarks/BookmarksViewController.swift @@ -10,7 +10,7 @@ import UIKit import Pachyderm import CoreData -class BookmarksViewController: UIViewController, CollectionViewController { +class BookmarksViewController: UIViewController, CollectionViewController, RefreshableViewController { private static let pageSize = 40 @@ -95,6 +95,13 @@ class BookmarksViewController: UIViewController, CollectionViewController { override func viewDidLoad() { super.viewDidLoad() + #if !targetEnvironment(macCatalyst) + collectionView.refreshControl = UIRefreshControl() + collectionView.refreshControl!.addTarget(self, action: #selector(refresh), for: .valueChanged) + #endif + + addKeyCommand(MenuController.refreshCommand(discoverabilityTitle: "Refresh Bookmarks")) + NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(managedObjectsDidChange), name: .NSManagedObjectContextObjectsDidChange, object: mastodonController.persistentContainer.viewContext) } @@ -176,8 +183,6 @@ class BookmarksViewController: UIViewController, CollectionViewController { snapshot.deleteItems([.loadingIndicator]) snapshot.appendItems(statuses.map { .status(id: $0.id, state: .unknown, addedLocally: false) }) await apply(snapshot: snapshot, animatingDifferences: true) - - state = .loaded } catch { let config = ToastConfiguration(from: error, with: "Error Loading Older Bookmarks", in: self) { [weak self] toast in toast.dismissToast(animated: true) @@ -187,9 +192,9 @@ class BookmarksViewController: UIViewController, CollectionViewController { snapshot.deleteItems([.loadingIndicator]) await apply(snapshot: snapshot, animatingDifferences: false) - - state = .loaded } + + state = .loaded } @objc private func handleStatusDeleted(_ notification: Foundation.Notification) { @@ -248,6 +253,67 @@ class BookmarksViewController: UIViewController, CollectionViewController { } } } + + // MARK: Interaction + + @objc func refresh() { + guard case .loaded = state, + let newer else { + #if !targetEnvironment(macCatalyst) + collectionView.refreshControl!.endRefreshing() + #endif + return + } + state = .loadingNewer + + Task { + + do { + let req = Client.getBookmarks(range: newer.withCount(BookmarksViewController.pageSize)) + let (statuses, pagination) = try await mastodonController.run(req) + self.newer = pagination?.newer + + await mastodonController.persistentContainer.addAll(statuses: statuses) + + var snapshot = dataSource.snapshot() + let localItems: [String: CollapseState] = Dictionary(uniqueKeysWithValues: snapshot.itemIdentifiers.compactMap({ + if case .status(id: let id, state: let state, addedLocally: true) = $0 { + return (id, state) + } else { + return nil + } + })) + var newItems: [Item] = [] + for status in statuses { + let state: CollapseState + if let existing = localItems[status.id] { + state = existing + snapshot.deleteItems([.status(id: status.id, state: existing, addedLocally: true)]) + } else { + state = .unknown + } + newItems.append(.status(id: status.id, state: state, addedLocally: false)) + } + if let first = snapshot.itemIdentifiers.first { + snapshot.insertItems(newItems, beforeItem: first) + } else { + snapshot.appendItems(newItems) + } + await apply(snapshot: snapshot, animatingDifferences: true) + } catch { + let config = ToastConfiguration(from: error, with: "Error Refreshing Bookmarks", in: self) { [weak self] toast in + toast.dismissToast(animated: true) + self?.refresh() + } + showToast(configuration: config, animated: true) + } + + #if !targetEnvironment(macCatalyst) + collectionView.refreshControl!.endRefreshing() + #endif + state = .loaded + } + } } @@ -297,6 +363,7 @@ extension BookmarksViewController { case loadingInitial case loaded case loadingOlder + case loadingNewer } }