From fa1daa682fc94630392e5c4ae48b5d10de9d21e5 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Mon, 13 Apr 2020 22:51:21 -0400 Subject: [PATCH] Convert profile VC to use CoreData objects Does not yet remove old statuses when scrolling up, like timeline VC --- .../MastodonCachePersistentStore.swift | 10 +- .../Profile/ProfileTableViewController.swift | 93 ++++++++++--------- .../ProfileHeaderTableViewCell.swift | 66 +++++++------ 3 files changed, 88 insertions(+), 81 deletions(-) diff --git a/Tusker/CoreData/MastodonCachePersistentStore.swift b/Tusker/CoreData/MastodonCachePersistentStore.swift index 33b4acfc..3c74e7ad 100644 --- a/Tusker/CoreData/MastodonCachePersistentStore.swift +++ b/Tusker/CoreData/MastodonCachePersistentStore.swift @@ -51,12 +51,13 @@ class MastodonCachePersistentStore: NSPersistentContainer { } } - func addOrUpdate(status: Status, incrementReferenceCount: Bool, save: Bool = true) { + func addOrUpdate(status: Status, incrementReferenceCount: Bool, completion: (() -> Void)?) { backgroundContext.perform { self.upsert(status: status, incrementReferenceCount: incrementReferenceCount) - if save, self.backgroundContext.hasChanges { + if self.backgroundContext.hasChanges { try! self.backgroundContext.save() } + completion?() } } @@ -90,12 +91,13 @@ class MastodonCachePersistentStore: NSPersistentContainer { } } - func addOrUpdate(account: Account, save: Bool = true) { + func addOrUpdate(account: Account, completion: (() -> Void)?) { backgroundContext.perform { self.upsert(account: account) - if save, self.backgroundContext.hasChanges { + if self.backgroundContext.hasChanges { try! self.backgroundContext.save() } + completion?() } } diff --git a/Tusker/Screens/Profile/ProfileTableViewController.swift b/Tusker/Screens/Profile/ProfileTableViewController.swift index 24e1224e..c0387b15 100644 --- a/Tusker/Screens/Profile/ProfileTableViewController.swift +++ b/Tusker/Screens/Profile/ProfileTableViewController.swift @@ -73,13 +73,13 @@ class ProfileTableViewController: EnhancedTableViewController { tableView.prefetchDataSource = self if let accountID = accountID { - if mastodonController.cache.account(for: accountID) != nil { + if mastodonController.persistentContainer.account(for: accountID) != nil { updateAccountUI() } else { loadingVC = LoadingViewController() embedChild(loadingVC!) mastodonController.cache.account(for: accountID) { (account) in - guard account != nil else { + guard let account = account else { let alert = UIAlertController(title: "Something Went Wrong", message: "Couldn't load the selected account", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (_) in self.navigationController!.popViewController(animated: true) @@ -89,9 +89,11 @@ class ProfileTableViewController: EnhancedTableViewController { } return } - DispatchQueue.main.async { - self.updateAccountUI() - self.tableView.reloadData() + self.mastodonController.persistentContainer.addOrUpdate(account: account) { + DispatchQueue.main.async { + self.updateAccountUI() + self.tableView.reloadData() + } } } } @@ -112,18 +114,20 @@ class ProfileTableViewController: EnhancedTableViewController { getStatuses(onlyPinned: true) { (response) in guard case let .success(statuses, _) = response else { fatalError() } - self.mastodonController.cache.addAll(statuses: statuses) - self.pinnedStatuses = statuses.map { ($0.id, .unknown) } + self.mastodonController.persistentContainer.addAll(statuses: statuses) { + self.pinnedStatuses = statuses.map { ($0.id, .unknown) } + } } getStatuses() { response in guard case let .success(statuses, pagination) = response else { fatalError() } - self.mastodonController.cache.addAll(statuses: statuses) - self.timelineSegments.append(statuses.map { ($0.id, .unknown) }) - - self.older = pagination?.older - self.newer = pagination?.newer + self.mastodonController.persistentContainer.addAll(statuses: statuses) { + self.timelineSegments.append(statuses.map { ($0.id, .unknown) }) + + self.older = pagination?.older + self.newer = pagination?.newer + } } } @@ -138,7 +142,7 @@ class ProfileTableViewController: EnhancedTableViewController { } func sendMessageMentioning() { - guard let account = mastodonController.cache.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") } + guard let account = mastodonController.persistentContainer.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") } let vc = UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct, mastodonController: mastodonController)) present(vc, animated: true) } @@ -152,7 +156,7 @@ class ProfileTableViewController: EnhancedTableViewController { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if section == 0 { - return accountID == nil || mastodonController.cache.account(for: accountID) == nil ? 0 : 1 + return accountID == nil || mastodonController.persistentContainer.account(for: accountID) == nil ? 0 : 1 } else if section == 1 { return pinnedStatuses.count } else { @@ -187,16 +191,20 @@ class ProfileTableViewController: EnhancedTableViewController { // MARK: - Table view delegate override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + // todo: if scrolling up, remove statuses at bottom like timeline VC + + // load older statuses if at bottom if timelineSegments.count > 0 && indexPath.section - 1 == timelineSegments.count && indexPath.row == timelineSegments[indexPath.section - 2].count - 1 { guard let older = older else { return } getStatuses(for: older) { response in guard case let .success(newStatuses, pagination) = response else { fatalError() } - self.mastodonController.cache.addAll(statuses: newStatuses) - self.timelineSegments[indexPath.section - 2].append(contentsOf: newStatuses.map { ($0.id, .unknown) }) - - self.older = pagination?.older + self.mastodonController.persistentContainer.addAll(statuses: newStatuses) { + self.timelineSegments[indexPath.section - 2].append(contentsOf: newStatuses.map { ($0.id, .unknown) }) + + self.older = pagination?.older + } } } } @@ -219,34 +227,35 @@ class ProfileTableViewController: EnhancedTableViewController { getStatuses(for: newer) { response in guard case let .success(newStatuses, pagination) = response else { fatalError() } - self.mastodonController.cache.addAll(statuses: newStatuses) - self.timelineSegments[0].insert(contentsOf: newStatuses.map { ($0.id, .unknown) }, at: 0) - - if let newer = pagination?.newer { - self.newer = newer - } - - DispatchQueue.main.async { - self.refreshControl?.endRefreshing() + self.mastodonController.persistentContainer.addAll(statuses: newStatuses) { + self.timelineSegments[0].insert(contentsOf: newStatuses.map { ($0.id, .unknown) }, at: 0) + + if let newer = pagination?.newer { + self.newer = newer + } + + DispatchQueue.main.async { + self.refreshControl?.endRefreshing() + } } } getStatuses(onlyPinned: true) { (response) in guard case let .success(newPinnedStatuses, _) = response else { fatalError() } - self.mastodonController.cache.addAll(statuses: newPinnedStatuses) - - let oldPinnedStatuses = self.pinnedStatuses - var pinnedStatuses = [(id: String, state: StatusState)]() - for status in newPinnedStatuses { - let state: StatusState - if let (_, oldState) = oldPinnedStatuses.first(where: { $0.id == status.id }) { - state = oldState - } else { - state = .unknown + self.mastodonController.persistentContainer.addAll(statuses: newPinnedStatuses) { + let oldPinnedStatuses = self.pinnedStatuses + var pinnedStatuses = [(id: String, state: StatusState)]() + for status in newPinnedStatuses { + let state: StatusState + if let (_, oldState) = oldPinnedStatuses.first(where: { $0.id == status.id }) { + state = oldState + } else { + state = .unknown + } + pinnedStatuses.append((status.id, state)) } - pinnedStatuses.append((status.id, state)) + self.pinnedStatuses = pinnedStatuses } - self.pinnedStatuses = pinnedStatuses } } @@ -268,7 +277,7 @@ extension ProfileTableViewController: StatusTableViewCellDelegate { extension ProfileTableViewController: ProfileHeaderTableViewCellDelegate { func showMoreOptions(cell: ProfileHeaderTableViewCell) { - let account = mastodonController.cache.account(for: accountID)! + let account = mastodonController.persistentContainer.account(for: accountID)! mastodonController.cache.relationship(for: account.id) { [weak self] (relationship) in guard let self = self else { return } @@ -293,7 +302,7 @@ extension ProfileTableViewController: UITableViewDataSourcePrefetching { func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) { for indexPath in indexPaths where indexPath.section > 1 { let statusID = timelineSegments[indexPath.section - 2][indexPath.row].id - guard let status = mastodonController.cache.status(for: statusID) else { continue } + guard let status = mastodonController.persistentContainer.status(for: statusID) else { continue } _ = ImageCache.avatars.get(status.account.avatar, completion: nil) for attachment in status.attachments { _ = ImageCache.attachments.get(attachment.url, completion: nil) @@ -304,7 +313,7 @@ extension ProfileTableViewController: UITableViewDataSourcePrefetching { func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) { for indexPath in indexPaths where indexPath.section > 1 { let statusID = timelineSegments[indexPath.section - 2][indexPath.row].id - guard let status = mastodonController.cache.status(for: statusID) else { continue } + guard let status = mastodonController.persistentContainer.status(for: statusID) else { continue } ImageCache.avatars.cancelWithoutCallback(status.account.avatar) for attachment in status.attachments { ImageCache.attachments.cancelWithoutCallback(attachment.url) diff --git a/Tusker/Views/Profile Header/ProfileHeaderTableViewCell.swift b/Tusker/Views/Profile Header/ProfileHeaderTableViewCell.swift index 38ec75d4..d881683b 100644 --- a/Tusker/Views/Profile Header/ProfileHeaderTableViewCell.swift +++ b/Tusker/Views/Profile Header/ProfileHeaderTableViewCell.swift @@ -63,7 +63,7 @@ class ProfileHeaderTableViewCell: UITableViewCell { guard accountID != self.accountID else { return } self.accountID = accountID - guard let account = mastodonController.cache.account(for: accountID) else { fatalError("Missing cached account \(accountID)") } + guard let account = mastodonController.persistentContainer.account(for: accountID) else { fatalError("Missing cached account \(accountID)") } updateUIForPreferences() @@ -101,42 +101,38 @@ class ProfileHeaderTableViewCell: UITableViewCell { } } - if let fields = account.fields, !fields.isEmpty { - fieldsStackView.isHidden = false - - fieldsStackView.arrangedSubviews.forEach { $0.removeFromSuperview() } - for field in fields { - let nameLabel = UILabel() - nameLabel.text = field.name - nameLabel.font = .boldSystemFont(ofSize: 17) - nameLabel.textAlignment = .right - nameLabel.numberOfLines = 0 - fieldNamesStackView.addArrangedSubview(nameLabel) - - let valueTextView = ContentTextView() - valueTextView.isSelectable = false - valueTextView.font = .systemFont(ofSize: 17) - valueTextView.setTextFromHtml(field.value) - valueTextView.setEmojis(account.emojis) - valueTextView.textAlignment = .left - valueTextView.awakeFromNib() - valueTextView.navigationDelegate = delegate - fieldValuesStack.addArrangedSubview(valueTextView) - } - } else { - fieldsStackView.isHidden = true - } - - if accountUpdater == nil { - accountUpdater = mastodonController.cache.accountSubject - .filter { [unowned self] in $0.id == self.accountID } - .receive(on: DispatchQueue.main) - .sink { [unowned self] in self.updateUI(for: $0.id) } + fieldsStackView.isHidden = account.fields.isEmpty + + fieldsStackView.arrangedSubviews.forEach { $0.removeFromSuperview() } + for field in account.fields { + let nameLabel = UILabel() + nameLabel.text = field.name + nameLabel.font = .boldSystemFont(ofSize: 17) + nameLabel.textAlignment = .right + nameLabel.numberOfLines = 0 + fieldNamesStackView.addArrangedSubview(nameLabel) + + let valueTextView = ContentTextView() + valueTextView.isSelectable = false + valueTextView.font = .systemFont(ofSize: 17) + valueTextView.setTextFromHtml(field.value) + valueTextView.setEmojis(account.emojis) + valueTextView.textAlignment = .left + valueTextView.awakeFromNib() + valueTextView.navigationDelegate = delegate + fieldValuesStack.addArrangedSubview(valueTextView) } + +// if accountUpdater == nil { +// accountUpdater = mastodonController.cache.accountSubject +// .filter { [unowned self] in $0.id == self.accountID } +// .receive(on: DispatchQueue.main) +// .sink { [unowned self] in self.updateUI(for: $0.id) } +// } } @objc func updateUIForPreferences() { - guard let account = mastodonController.cache.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") } + guard let account = mastodonController.persistentContainer.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") } avatarContainerView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarContainerView) avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView) @@ -153,12 +149,12 @@ class ProfileHeaderTableViewCell: UITableViewCell { } @objc func avatarPressed() { - guard let account = mastodonController.cache.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") } + guard let account = mastodonController.persistentContainer.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") } delegate?.showLoadingLargeImage(url: account.avatar, cache: .avatars, description: nil, animatingFrom: avatarImageView) } @objc func headerPressed() { - guard let account = mastodonController.cache.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") } + guard let account = mastodonController.persistentContainer.account(for: accountID) else { fatalError("Missing cached account \(accountID!)") } delegate?.showLoadingLargeImage(url: account.header, cache: .headers, description: nil, animatingFrom: headerImageView) }