forked from shadowfacts/Tusker
parent
2464e2530f
commit
bd21e88e8b
|
@ -175,6 +175,14 @@ public class InstanceFeatures: ObservableObject {
|
||||||
hasMastodonVersion(2, 8, 0)
|
hasMastodonVersion(2, 8, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var listRepliesPolicy: Bool {
|
||||||
|
hasMastodonVersion(3, 3, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
public var exclusiveLists: Bool {
|
||||||
|
hasMastodonVersion(4, 2, 0) || instanceType.isMastodon(.hometown(nil))
|
||||||
|
}
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -264,6 +264,7 @@
|
||||||
D6BD395B29B64441005FFD2B /* ComposeHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BD395A29B64441005FFD2B /* ComposeHostingController.swift */; };
|
D6BD395B29B64441005FFD2B /* ComposeHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BD395A29B64441005FFD2B /* ComposeHostingController.swift */; };
|
||||||
D6BEA245291A0EDE002F4D01 /* Duckable in Frameworks */ = {isa = PBXBuildFile; productRef = D6BEA244291A0EDE002F4D01 /* Duckable */; };
|
D6BEA245291A0EDE002F4D01 /* Duckable in Frameworks */ = {isa = PBXBuildFile; productRef = D6BEA244291A0EDE002F4D01 /* Duckable */; };
|
||||||
D6BEA247291A0F2D002F4D01 /* Duckable+Root.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BEA246291A0F2D002F4D01 /* Duckable+Root.swift */; };
|
D6BEA247291A0F2D002F4D01 /* Duckable+Root.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BEA246291A0F2D002F4D01 /* Duckable+Root.swift */; };
|
||||||
|
D6C041C42AED77730094D32D /* EditListSettingsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C041C32AED77730094D32D /* EditListSettingsService.swift */; };
|
||||||
D6C1B2082545D1EC00DAAA66 /* StatusCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C1B2072545D1EC00DAAA66 /* StatusCardView.swift */; };
|
D6C1B2082545D1EC00DAAA66 /* StatusCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C1B2072545D1EC00DAAA66 /* StatusCardView.swift */; };
|
||||||
D6C3F4F5298ED0890009FCFF /* LocalPredicateStatusesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C3F4F4298ED0890009FCFF /* LocalPredicateStatusesViewController.swift */; };
|
D6C3F4F5298ED0890009FCFF /* LocalPredicateStatusesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C3F4F4298ED0890009FCFF /* LocalPredicateStatusesViewController.swift */; };
|
||||||
D6C3F4F7298ED7F70009FCFF /* FavoritesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C3F4F6298ED7F70009FCFF /* FavoritesViewController.swift */; };
|
D6C3F4F7298ED7F70009FCFF /* FavoritesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C3F4F6298ED7F70009FCFF /* FavoritesViewController.swift */; };
|
||||||
|
@ -665,6 +666,7 @@
|
||||||
D6BD395C29B789D5005FFD2B /* TuskerComponents */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = TuskerComponents; path = Packages/TuskerComponents; sourceTree = "<group>"; };
|
D6BD395C29B789D5005FFD2B /* TuskerComponents */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = TuskerComponents; path = Packages/TuskerComponents; sourceTree = "<group>"; };
|
||||||
D6BEA243291A0C83002F4D01 /* Duckable */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Duckable; path = Packages/Duckable; sourceTree = "<group>"; };
|
D6BEA243291A0C83002F4D01 /* Duckable */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Duckable; path = Packages/Duckable; sourceTree = "<group>"; };
|
||||||
D6BEA246291A0F2D002F4D01 /* Duckable+Root.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Duckable+Root.swift"; sourceTree = "<group>"; };
|
D6BEA246291A0F2D002F4D01 /* Duckable+Root.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Duckable+Root.swift"; sourceTree = "<group>"; };
|
||||||
|
D6C041C32AED77730094D32D /* EditListSettingsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditListSettingsService.swift; sourceTree = "<group>"; };
|
||||||
D6C1B2072545D1EC00DAAA66 /* StatusCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusCardView.swift; sourceTree = "<group>"; };
|
D6C1B2072545D1EC00DAAA66 /* StatusCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusCardView.swift; sourceTree = "<group>"; };
|
||||||
D6C3F4F4298ED0890009FCFF /* LocalPredicateStatusesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalPredicateStatusesViewController.swift; sourceTree = "<group>"; };
|
D6C3F4F4298ED0890009FCFF /* LocalPredicateStatusesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalPredicateStatusesViewController.swift; sourceTree = "<group>"; };
|
||||||
D6C3F4F6298ED7F70009FCFF /* FavoritesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesViewController.swift; sourceTree = "<group>"; };
|
D6C3F4F6298ED7F70009FCFF /* FavoritesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1636,6 +1638,7 @@
|
||||||
D621733228F1D5ED004C7DB1 /* ReblogService.swift */,
|
D621733228F1D5ED004C7DB1 /* ReblogService.swift */,
|
||||||
D6F6A54F291F058600F496A8 /* CreateListService.swift */,
|
D6F6A54F291F058600F496A8 /* CreateListService.swift */,
|
||||||
D6F6A551291F098700F496A8 /* RenameListService.swift */,
|
D6F6A551291F098700F496A8 /* RenameListService.swift */,
|
||||||
|
D6C041C32AED77730094D32D /* EditListSettingsService.swift */,
|
||||||
D6F6A553291F0D9600F496A8 /* DeleteListService.swift */,
|
D6F6A553291F0D9600F496A8 /* DeleteListService.swift */,
|
||||||
D61F75952937037800C0B37F /* ToggleFollowHashtagService.swift */,
|
D61F75952937037800C0B37F /* ToggleFollowHashtagService.swift */,
|
||||||
D61F75B0293BD85300C0B37F /* CreateFilterService.swift */,
|
D61F75B0293BD85300C0B37F /* CreateFilterService.swift */,
|
||||||
|
@ -2140,6 +2143,7 @@
|
||||||
D6F4D79429ECB0AF00351B87 /* UIBackgroundConfiguration+AppColors.swift in Sources */,
|
D6F4D79429ECB0AF00351B87 /* UIBackgroundConfiguration+AppColors.swift in Sources */,
|
||||||
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */,
|
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */,
|
||||||
D681A29A249AD62D0085E54E /* LargeImageContentView.swift in Sources */,
|
D681A29A249AD62D0085E54E /* LargeImageContentView.swift in Sources */,
|
||||||
|
D6C041C42AED77730094D32D /* EditListSettingsService.swift in Sources */,
|
||||||
D6DFC69E242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift in Sources */,
|
D6DFC69E242C490400ACC392 /* TrackpadScrollGestureRecognizer.swift in Sources */,
|
||||||
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */,
|
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */,
|
||||||
D6D3F4C424FDB6B700EC4A6A /* View+ConditionalModifier.swift in Sources */,
|
D6D3F4C424FDB6B700EC4A6A /* View+ConditionalModifier.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
//
|
||||||
|
// EditListSettingsService.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 10/28/23.
|
||||||
|
// Copyright © 2023 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
class EditListSettingsService {
|
||||||
|
private let list: ListProtocol
|
||||||
|
private let mastodonController: MastodonController
|
||||||
|
private let present: (UIViewController) -> Void
|
||||||
|
|
||||||
|
init(list: ListProtocol, mastodonController: MastodonController, present: @escaping (UIViewController) -> Void) {
|
||||||
|
self.list = list
|
||||||
|
self.mastodonController = mastodonController
|
||||||
|
self.present = present
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(title: String? = nil, replyPolicy: List.ReplyPolicy? = nil, exclusive: Bool? = nil) async {
|
||||||
|
do {
|
||||||
|
let req = List.update(
|
||||||
|
list.id,
|
||||||
|
title: title ?? list.title,
|
||||||
|
replyPolicy: replyPolicy ?? list.replyPolicy,
|
||||||
|
exclusive: exclusive ?? list.exclusive
|
||||||
|
)
|
||||||
|
let (list, _) = try await mastodonController.run(req)
|
||||||
|
mastodonController.updatedList(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.run(title: title, replyPolicy: replyPolicy, exclusive: exclusive)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
present(alert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -471,7 +471,7 @@ class MastodonController: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func renamedList(_ list: List) {
|
func updatedList(_ list: List) {
|
||||||
var new = self.lists
|
var new = self.lists
|
||||||
if let index = new.firstIndex(where: { $0.id == list.id }) {
|
if let index = new.firstIndex(where: { $0.id == list.id }) {
|
||||||
new[index] = list
|
new[index] = list
|
||||||
|
|
|
@ -49,7 +49,7 @@ class RenameListService {
|
||||||
do {
|
do {
|
||||||
let req = List.update(list.id, title: title, replyPolicy: nil, exclusive: nil)
|
let req = List.update(list.id, title: title, replyPolicy: nil, exclusive: nil)
|
||||||
let (list, _) = try await mastodonController.run(req)
|
let (list, _) = try await mastodonController.run(req)
|
||||||
mastodonController.renamedList(list)
|
mastodonController.updatedList(list)
|
||||||
} catch {
|
} catch {
|
||||||
let alert = UIAlertController(title: "Error Updating List", message: error.localizedDescription, preferredStyle: .alert)
|
let alert = UIAlertController(title: "Error Updating List", message: error.localizedDescription, preferredStyle: .alert)
|
||||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||||
|
|
|
@ -38,7 +38,6 @@ class EditListAccountsViewController: UIViewController, CollectionViewController
|
||||||
|
|
||||||
listRenamedCancellable = mastodonController.$lists
|
listRenamedCancellable = mastodonController.$lists
|
||||||
.compactMap { $0.first { $0.id == list.id } }
|
.compactMap { $0.first { $0.id == list.id } }
|
||||||
.removeDuplicates(by: { $0.title == $1.title })
|
|
||||||
.sink { [unowned self] in
|
.sink { [unowned self] in
|
||||||
self.list = $0
|
self.list = $0
|
||||||
self.listChanged()
|
self.listChanged()
|
||||||
|
@ -103,9 +102,27 @@ class EditListAccountsViewController: UIViewController, CollectionViewController
|
||||||
navigationItem.hidesSearchBarWhenScrolling = false
|
navigationItem.hidesSearchBarWhenScrolling = false
|
||||||
if #available(iOS 16.0, *) {
|
if #available(iOS 16.0, *) {
|
||||||
navigationItem.preferredSearchBarPlacement = .stacked
|
navigationItem.preferredSearchBarPlacement = .stacked
|
||||||
|
|
||||||
|
navigationItem.renameDelegate = self
|
||||||
|
navigationItem.titleMenuProvider = { [unowned self] suggested in
|
||||||
|
var children = suggested
|
||||||
|
children.append(contentsOf: self.listSettingsMenuElements())
|
||||||
|
return UIMenu(children: children)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Edit", menu: UIMenu(children: [
|
||||||
|
// uncached so that menu always reflects the current state of the list
|
||||||
|
UIDeferredMenuElement.uncached({ [unowned self] elementHandler in
|
||||||
|
var elements = self.listSettingsMenuElements()
|
||||||
|
elements.insert(UIAction(title: "Rename…", image: UIImage(systemName: "pencil"), handler: { [unowned self] _ in
|
||||||
|
RenameListService(list: self.list, mastodonController: self.mastodonController, present: {
|
||||||
|
self.present($0, animated: true)
|
||||||
|
}).run()
|
||||||
|
}), at: 0)
|
||||||
|
elementHandler(elements)
|
||||||
|
})
|
||||||
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
navigationItem.rightBarButtonItem = UIBarButtonItem(title: NSLocalizedString("Rename", comment: "rename list button title"), style: .plain, target: self, action: #selector(renameButtonPressed))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
||||||
|
@ -140,7 +157,31 @@ class EditListAccountsViewController: UIViewController, CollectionViewController
|
||||||
}
|
}
|
||||||
|
|
||||||
private func listChanged() {
|
private func listChanged() {
|
||||||
title = String(format: NSLocalizedString("Edit %@", comment: "edit list screen title"), list.title)
|
title = list.title
|
||||||
|
}
|
||||||
|
|
||||||
|
private func listSettingsMenuElements() -> [UIMenuElement] {
|
||||||
|
var elements = [UIMenuElement]()
|
||||||
|
if mastodonController.instanceFeatures.listRepliesPolicy {
|
||||||
|
let actions = List.ReplyPolicy.allCases.map { policy in
|
||||||
|
UIAction(title: policy.actionTitle, state: list.replyPolicy == policy ? .on : .off) { [unowned self] _ in
|
||||||
|
self.setReplyPolicy(policy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elements.append(UIMenu(title: "Show replies…", image: UIImage(systemName: "arrowshape.turn.up.left"), children: actions))
|
||||||
|
}
|
||||||
|
if mastodonController.instanceFeatures.exclusiveLists {
|
||||||
|
let actions = [
|
||||||
|
UIAction(title: "Hidden from Home", state: list.exclusive == true ? .on : .off) { [unowned self] _ in
|
||||||
|
self.setExclusive(true)
|
||||||
|
},
|
||||||
|
UIAction(title: "Shown on Home", state: list.exclusive == false ? .on : .off) { [unowned self] _ in
|
||||||
|
self.setExclusive(false)
|
||||||
|
},
|
||||||
|
]
|
||||||
|
elements.append(UIMenu(title: "Posts from this list are…", children: actions))
|
||||||
|
}
|
||||||
|
return elements
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
|
@ -254,13 +295,21 @@ class EditListAccountsViewController: UIViewController, CollectionViewController
|
||||||
self.showToast(configuration: config, animated: true)
|
self.showToast(configuration: config, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Interaction
|
|
||||||
|
|
||||||
@objc func renameButtonPressed() {
|
private func setReplyPolicy(_ replyPolicy: List.ReplyPolicy) {
|
||||||
RenameListService(list: list, mastodonController: mastodonController, present: { self.present($0, animated: true) }).run()
|
Task {
|
||||||
|
let service = EditListSettingsService(list: list, mastodonController: mastodonController, present: { self.present($0, animated: true) })
|
||||||
|
await service.run(replyPolicy: replyPolicy)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func setExclusive(_ exclusive: Bool) {
|
||||||
|
Task {
|
||||||
|
let service = EditListSettingsService(list: list, mastodonController: mastodonController, present: { self.present($0, animated: true) })
|
||||||
|
await service.run(exclusive: exclusive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension EditListAccountsViewController {
|
extension EditListAccountsViewController {
|
||||||
|
@ -310,3 +359,29 @@ extension EditListAccountsViewController: SearchResultsViewControllerDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension EditListAccountsViewController: UINavigationItemRenameDelegate {
|
||||||
|
func navigationItem(_: UINavigationItem, shouldEndRenamingWith title: String) -> Bool {
|
||||||
|
!title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
func navigationItem(_: UINavigationItem, didEndRenamingWith title: String) {
|
||||||
|
Task {
|
||||||
|
let service = EditListSettingsService(list: list, mastodonController: mastodonController, present: { self.present($0, animated: true) })
|
||||||
|
await service.run(title: title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension List.ReplyPolicy {
|
||||||
|
var actionTitle: String {
|
||||||
|
switch self {
|
||||||
|
case .followed:
|
||||||
|
"To accounts you follow"
|
||||||
|
case .list:
|
||||||
|
"To other list members"
|
||||||
|
case .none:
|
||||||
|
"Never"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ class ListTimelineViewController: TimelineViewController {
|
||||||
|
|
||||||
func presentEdit(animated: Bool) {
|
func presentEdit(animated: Bool) {
|
||||||
let editListAccountsController = EditListAccountsViewController(list: list, mastodonController: mastodonController)
|
let editListAccountsController = EditListAccountsViewController(list: list, mastodonController: mastodonController)
|
||||||
editListAccountsController.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(editListDoneButtonPressed))
|
editListAccountsController.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(editListDoneButtonPressed))
|
||||||
let navController = UINavigationController(rootViewController: editListAccountsController)
|
let navController = UINavigationController(rootViewController: editListAccountsController)
|
||||||
present(navController, animated: animated)
|
present(navController, animated: animated)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue