diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 48db7a57b..075de3e34 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -313,6 +313,7 @@ D6F6A54C291EF6FE00F496A8 /* EditListSearchResultsContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F6A54B291EF6FE00F496A8 /* EditListSearchResultsContainerViewController.swift */; }; D6F6A54E291EF7E100F496A8 /* EditListSearchFollowingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F6A54D291EF7E100F496A8 /* EditListSearchFollowingViewController.swift */; }; D6F6A550291F058600F496A8 /* CreateListService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F6A54F291F058600F496A8 /* CreateListService.swift */; }; + D6F6A552291F098700F496A8 /* RenameListService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F6A551291F098700F496A8 /* RenameListService.swift */; }; D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EF21251A2900CF0F2B /* MastodonController.swift */; }; D6FF9860255C717400845181 /* AccountSwitchingContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6FF985F255C717400845181 /* AccountSwitchingContainerViewController.swift */; }; /* End PBXBuildFile section */ @@ -681,6 +682,7 @@ D6F6A54B291EF6FE00F496A8 /* EditListSearchResultsContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditListSearchResultsContainerViewController.swift; sourceTree = ""; }; D6F6A54D291EF7E100F496A8 /* EditListSearchFollowingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditListSearchFollowingViewController.swift; sourceTree = ""; }; D6F6A54F291F058600F496A8 /* CreateListService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateListService.swift; sourceTree = ""; }; + D6F6A551291F098700F496A8 /* RenameListService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenameListService.swift; sourceTree = ""; }; D6F953EF21251A2900CF0F2B /* MastodonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonController.swift; sourceTree = ""; }; D6FF985F255C717400845181 /* AccountSwitchingContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSwitchingContainerViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1491,6 +1493,7 @@ D61ABEFD28F1C92600B29151 /* FavoriteService.swift */, D621733228F1D5ED004C7DB1 /* ReblogService.swift */, D6F6A54F291F058600F496A8 /* CreateListService.swift */, + D6F6A551291F098700F496A8 /* RenameListService.swift */, ); path = API; sourceTree = ""; @@ -1801,6 +1804,7 @@ D627944F23A9C99800D38C68 /* EditListAccountsViewController.swift in Sources */, D6945C3423AC6431005C403C /* AddSavedHashtagViewController.swift in Sources */, D627943723A552C200D38C68 /* BookmarkStatusActivity.swift in Sources */, + D6F6A552291F098700F496A8 /* RenameListService.swift in Sources */, D68ACE5D279B1ABA001CE8EB /* AssetPickerControlCollectionViewCell.swift in Sources */, D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */, D6AC956723C4347E008C9946 /* MainSceneDelegate.swift in Sources */, diff --git a/Tusker/API/RenameListService.swift b/Tusker/API/RenameListService.swift new file mode 100644 index 000000000..835a62450 --- /dev/null +++ b/Tusker/API/RenameListService.swift @@ -0,0 +1,69 @@ +// +// RenameListService.swift +// Tusker +// +// Created by Shadowfacts on 11/11/22. +// Copyright © 2022 Shadowfacts. All rights reserved. +// + +import UIKit +import Pachyderm + +@MainActor +class RenameListService { + private let list: List + private let mastodonController: MastodonController + private let present: (UIViewController) -> Void + + private var renameAction: UIAlertAction? + + init(list: List, mastodonController: MastodonController, present: @escaping (UIViewController) -> Void) { + self.list = list + self.mastodonController = mastodonController + self.present = present + } + + func run() { + let alert = UIAlertController(title: NSLocalizedString("Rename List", comment: "rename list alert title"), message: nil, preferredStyle: .alert) + alert.addTextField { (textField) in + textField.text = self.list.title + textField.addTarget(self, action: #selector(self.alertTextFieldValueChanged), for: .editingChanged) + } + alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "rename list alert cancel button"), style: .cancel, handler: nil)) + renameAction = UIAlertAction(title: NSLocalizedString("Rename", comment: "renaem list alert rename button"), style: .default, handler: { (_) in + let textField = alert.textFields!.first! + let title = textField.text ?? "" + Task { + await self.updateList(with: title) + } + }) + alert.addAction(renameAction!) + present(alert) + } + + @objc private func alertTextFieldValueChanged(_ textField: UITextField) { + renameAction?.isEnabled = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false + } + + private func updateList(with title: String) async { + do { + let req = List.update(list, title: title) + let (list, _) = try await mastodonController.run(req) + NotificationCenter.default.post(name: .listRenamed, object: list.id, userInfo: ["list": list]) + } catch { + let alert = UIAlertController(title: "Error Updating List", message: error.localizedDescription, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + alert.addAction(UIAlertAction(title: "Retry", style: .default, handler: { _ in + Task { + await self.updateList(with: title) + } + })) + present(alert) + } + } + +} + +extension Foundation.Notification.Name { + static let listRenamed = Notification.Name("listRenamed") +} diff --git a/Tusker/Screens/Explore/ExploreViewController.swift b/Tusker/Screens/Explore/ExploreViewController.swift index 3aa811803..6d24fb75d 100644 --- a/Tusker/Screens/Explore/ExploreViewController.swift +++ b/Tusker/Screens/Explore/ExploreViewController.swift @@ -71,6 +71,7 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate { NotificationCenter.default.addObserver(self, selector: #selector(savedHashtagsChanged), name: .savedHashtagsChanged, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(savedInstancesChanged), name: .savedInstancesChanged, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(reloadLists), name: .listsChanged, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(listRenamed(_:)), name: .listRenamed, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil) } @@ -197,6 +198,23 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate { } } + @objc private func listRenamed(_ notification: Foundation.Notification) { + let list = notification.userInfo!["list"] as! List + var snapshot = dataSource.snapshot() + let existing = snapshot.itemIdentifiers(inSection: .lists).first(where: { + if case .list(let existingList) = $0, existingList.id == list.id { + return true + } else { + return false + } + }) + if let existing { + snapshot.insertItems([.list(list)], afterItem: existing) + snapshot.deleteItems([existing]) + dataSource.apply(snapshot) + } + } + @MainActor private func fetchSavedHashtags() -> [SavedHashtag] { let req = SavedHashtag.fetchRequest() @@ -490,7 +508,7 @@ extension ExploreViewController { case (.profileDirectory, .profileDirectory): return true case let (.list(a), .list(b)): - return a.id == b.id + return a.id == b.id && a.title == b.title case (.addList, .addList): return true case let (.savedHashtag(a), .savedHashtag(b)): @@ -521,6 +539,7 @@ extension ExploreViewController { case let .list(list): hasher.combine("list") hasher.combine(list.id) + hasher.combine(list.title) case .addList: hasher.combine("addList") case let .savedHashtag(hashtag): diff --git a/Tusker/Screens/Lists/EditListAccountsViewController.swift b/Tusker/Screens/Lists/EditListAccountsViewController.swift index 4be7cb47f..a6f20a77f 100644 --- a/Tusker/Screens/Lists/EditListAccountsViewController.swift +++ b/Tusker/Screens/Lists/EditListAccountsViewController.swift @@ -13,7 +13,7 @@ class EditListAccountsViewController: EnhancedTableViewController { let mastodonController: MastodonController - let list: List + private var list: List var dataSource: DataSource! @@ -28,7 +28,9 @@ class EditListAccountsViewController: EnhancedTableViewController { super.init(style: .plain) - title = String(format: NSLocalizedString("Edit %@", comment: "edit list screen title"), list.title) + listChanged() + + NotificationCenter.default.addObserver(self, selector: #selector(listRenamed(_:)), name: .listRenamed, object: list.id) } required init?(coder: NSCoder) { @@ -82,6 +84,16 @@ class EditListAccountsViewController: EnhancedTableViewController { } } + private func listChanged() { + title = String(format: NSLocalizedString("Edit %@", comment: "edit list screen title"), list.title) + } + + @objc private func listRenamed(_ notification: Foundation.Notification) { + let list = notification.userInfo!["list"] as! List + self.list = list + self.listChanged() + } + func loadAccounts() async { do { let request = List.getAccounts(list) @@ -149,24 +161,7 @@ class EditListAccountsViewController: EnhancedTableViewController { // MARK: - Interaction @objc func renameButtonPressed() { - let alert = UIAlertController(title: NSLocalizedString("Rename List", comment: "rename list alert title"), message: nil, preferredStyle: .alert) - alert.addTextField { (textField) in - textField.text = self.list.title - } - alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "rename list alert cancel button"), style: .cancel, handler: nil)) - alert.addAction(UIAlertAction(title: NSLocalizedString("Rename", comment: "renaem list alert rename button"), style: .default, handler: { (_) in - guard let text = alert.textFields?.first?.text else { - fatalError() - } - let request = List.update(self.list, title: text) - self.mastodonController.run(request) { (response) in - guard case .success(_, _) = response else { - fatalError() - } - // todo: show success message somehow - } - })) - present(alert, animated: true) + RenameListService(list: list, mastodonController: mastodonController, present: { self.present($0, animated: true) }).run() } } diff --git a/Tusker/Screens/Lists/ListTimelineViewController.swift b/Tusker/Screens/Lists/ListTimelineViewController.swift index ab97ce297..69bc51cc5 100644 --- a/Tusker/Screens/Lists/ListTimelineViewController.swift +++ b/Tusker/Screens/Lists/ListTimelineViewController.swift @@ -11,7 +11,7 @@ import Pachyderm class ListTimelineViewController: TimelineViewController { - let list: List + private(set) var list: List var presentEditOnAppear = false @@ -20,7 +20,9 @@ class ListTimelineViewController: TimelineViewController { super.init(for: .list(id: list.id), mastodonController: mastodonController) - title = list.title + listChanged() + + NotificationCenter.default.addObserver(self, selector: #selector(listRenamed(_:)), name: .listRenamed, object: list.id) } required init?(coder aDecoder: NSCoder) { @@ -41,6 +43,16 @@ class ListTimelineViewController: TimelineViewController { } } + private func listChanged() { + title = list.title + } + + @objc private func listRenamed(_ notification: Foundation.Notification) { + let list = notification.userInfo!["list"] as! List + self.list = list + self.listChanged() + } + func presentEdit(animated: Bool) { let editListAccountsController = EditListAccountsViewController(list: list, mastodonController: mastodonController) editListAccountsController.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(editListDoneButtonPressed)) diff --git a/Tusker/Screens/Main/MainSidebarViewController.swift b/Tusker/Screens/Main/MainSidebarViewController.swift index 53e48fe1e..e015a13f4 100644 --- a/Tusker/Screens/Main/MainSidebarViewController.swift +++ b/Tusker/Screens/Main/MainSidebarViewController.swift @@ -100,6 +100,7 @@ class MainSidebarViewController: UIViewController { NotificationCenter.default.addObserver(self, selector: #selector(reloadSavedHashtags), name: .savedHashtagsChanged, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(reloadSavedInstances), name: .savedInstancesChanged, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(reloadLists), name: .listsChanged, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(listRenamed(_:)), name: .listRenamed, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil) onViewDidLoad?() @@ -224,6 +225,23 @@ class MainSidebarViewController: UIViewController { } } + @objc private func listRenamed(_ notification: Foundation.Notification) { + let list = notification.userInfo!["list"] as! List + var snapshot = dataSource.snapshot() + let existing = snapshot.itemIdentifiers(inSection: .lists).first(where: { + if case .list(let existingList) = $0, existingList.id == list.id { + return true + } else { + return false + } + }) + if let existing { + snapshot.insertItems([.list(list)], afterItem: existing) + snapshot.deleteItems([existing]) + dataSource.apply(snapshot) + } + } + @MainActor private func fetchSavedHashtags() -> [SavedHashtag] { let req = SavedHashtag.fetchRequest()