Add preferences for status cell swipe actions

Closes #249
This commit is contained in:
Shadowfacts 2022-11-26 20:13:16 -05:00
parent c256fb4cbd
commit e04cdd16d6
8 changed files with 356 additions and 129 deletions

View File

@ -44,6 +44,8 @@
D61DC84628F498F200B82C6E /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61DC84528F498F200B82C6E /* Logging.swift */; }; D61DC84628F498F200B82C6E /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61DC84528F498F200B82C6E /* Logging.swift */; };
D61DC84B28F4FD2000B82C6E /* ProfileHeaderCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61DC84A28F4FD2000B82C6E /* ProfileHeaderCollectionViewCell.swift */; }; D61DC84B28F4FD2000B82C6E /* ProfileHeaderCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61DC84A28F4FD2000B82C6E /* ProfileHeaderCollectionViewCell.swift */; };
D61DC84D28F500D200B82C6E /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61DC84C28F500D200B82C6E /* ProfileViewController.swift */; }; D61DC84D28F500D200B82C6E /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61DC84C28F500D200B82C6E /* ProfileViewController.swift */; };
D61F75882932DB6000C0B37F /* StatusSwipeAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75872932DB6000C0B37F /* StatusSwipeAction.swift */; };
D61F758A2932E1FC00C0B37F /* SwipeActionsPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75892932E1FC00C0B37F /* SwipeActionsPrefsView.swift */; };
D620483423D3801D008A63EF /* LinkTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483323D3801D008A63EF /* LinkTextView.swift */; }; D620483423D3801D008A63EF /* LinkTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483323D3801D008A63EF /* LinkTextView.swift */; };
D620483623D38075008A63EF /* ContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483523D38075008A63EF /* ContentTextView.swift */; }; D620483623D38075008A63EF /* ContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483523D38075008A63EF /* ContentTextView.swift */; };
D620483823D38190008A63EF /* StatusContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483723D38190008A63EF /* StatusContentTextView.swift */; }; D620483823D38190008A63EF /* StatusContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483723D38190008A63EF /* StatusContentTextView.swift */; };
@ -406,6 +408,8 @@
D61DC84528F498F200B82C6E /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = "<group>"; }; D61DC84528F498F200B82C6E /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = "<group>"; };
D61DC84A28F4FD2000B82C6E /* ProfileHeaderCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderCollectionViewCell.swift; sourceTree = "<group>"; }; D61DC84A28F4FD2000B82C6E /* ProfileHeaderCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderCollectionViewCell.swift; sourceTree = "<group>"; };
D61DC84C28F500D200B82C6E /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = "<group>"; }; D61DC84C28F500D200B82C6E /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = "<group>"; };
D61F75872932DB6000C0B37F /* StatusSwipeAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusSwipeAction.swift; sourceTree = "<group>"; };
D61F75892932E1FC00C0B37F /* SwipeActionsPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeActionsPrefsView.swift; sourceTree = "<group>"; };
D620483323D3801D008A63EF /* LinkTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkTextView.swift; sourceTree = "<group>"; }; D620483323D3801D008A63EF /* LinkTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkTextView.swift; sourceTree = "<group>"; };
D620483523D38075008A63EF /* ContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentTextView.swift; sourceTree = "<group>"; }; D620483523D38075008A63EF /* ContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentTextView.swift; sourceTree = "<group>"; };
D620483723D38190008A63EF /* StatusContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentTextView.swift; sourceTree = "<group>"; }; D620483723D38190008A63EF /* StatusContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentTextView.swift; sourceTree = "<group>"; };
@ -1031,6 +1035,7 @@
D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */, D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */,
04586B4022B2FFB10021BD04 /* PreferencesView.swift */, 04586B4022B2FFB10021BD04 /* PreferencesView.swift */,
04586B4222B301470021BD04 /* AppearancePrefsView.swift */, 04586B4222B301470021BD04 /* AppearancePrefsView.swift */,
D61F75892932E1FC00C0B37F /* SwipeActionsPrefsView.swift */,
0427033722B30F5F000D31B6 /* BehaviorPrefsView.swift */, 0427033722B30F5F000D31B6 /* BehaviorPrefsView.swift */,
D6B17254254F88B800128392 /* OppositeCollapseKeywordsView.swift */, D6B17254254F88B800128392 /* OppositeCollapseKeywordsView.swift */,
D680153F2401A6BA00D6103B /* ComposingPrefsView.swift */, D680153F2401A6BA00D6103B /* ComposingPrefsView.swift */,
@ -1133,6 +1138,7 @@
D6DD353C22F28CD000A9563A /* ContentWarningCopyMode.swift */, D6DD353C22F28CD000A9563A /* ContentWarningCopyMode.swift */,
D6DD353E22F502EC00A9563A /* Preferences+Notification.swift */, D6DD353E22F502EC00A9563A /* Preferences+Notification.swift */,
D6BC9DB4232D4CE3002CA326 /* NotificationsMode.swift */, D6BC9DB4232D4CE3002CA326 /* NotificationsMode.swift */,
D61F75872932DB6000C0B37F /* StatusSwipeAction.swift */,
); );
path = Preferences; path = Preferences;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1849,6 +1855,7 @@
D6B9366F2828452F00237D0E /* SavedHashtag.swift in Sources */, D6B9366F2828452F00237D0E /* SavedHashtag.swift in Sources */,
D6B9366D2828445000237D0E /* SavedInstance.swift in Sources */, D6B9366D2828445000237D0E /* SavedInstance.swift in Sources */,
D60E2F272442372B005F8713 /* StatusMO.swift in Sources */, D60E2F272442372B005F8713 /* StatusMO.swift in Sources */,
D61F758A2932E1FC00C0B37F /* SwipeActionsPrefsView.swift in Sources */,
D60E2F2C24423EAD005F8713 /* LazilyDecoding.swift in Sources */, D60E2F2C24423EAD005F8713 /* LazilyDecoding.swift in Sources */,
D662AEF2263A4BE10082A153 /* ComposePollView.swift in Sources */, D662AEF2263A4BE10082A153 /* ComposePollView.swift in Sources */,
D677284A24ECBDF400C732D3 /* ComposeCurrentAccount.swift in Sources */, D677284A24ECBDF400C732D3 /* ComposeCurrentAccount.swift in Sources */,
@ -1952,6 +1959,7 @@
D6C82B5625C5F3F20017F1E6 /* ExpandThreadTableViewCell.swift in Sources */, D6C82B5625C5F3F20017F1E6 /* ExpandThreadTableViewCell.swift in Sources */,
D686BBE324FBF8110068E6AA /* WrappedProgressView.swift in Sources */, D686BBE324FBF8110068E6AA /* WrappedProgressView.swift in Sources */,
D620483423D3801D008A63EF /* LinkTextView.swift in Sources */, D620483423D3801D008A63EF /* LinkTextView.swift in Sources */,
D61F75882932DB6000C0B37F /* StatusSwipeAction.swift in Sources */,
D6D12B58292D5B2C00D528E1 /* StatusActionAccountListViewController.swift in Sources */, D6D12B58292D5B2C00D528E1 /* StatusActionAccountListViewController.swift in Sources */,
D6412B0D24B0D4CF00F5412E /* ProfileHeaderView.swift in Sources */, D6412B0D24B0D4CF00F5412E /* ProfileHeaderView.swift in Sources */,
D641C77F213DC78A004B4513 /* InlineTextAttachment.swift in Sources */, D641C77F213DC78A004B4513 /* InlineTextAttachment.swift in Sources */,

View File

@ -91,11 +91,20 @@ struct InstanceFeatures {
} else if nodeInfo?.software.name == "hometown" { } else if nodeInfo?.software.name == "hometown" {
var mastoVersion: Version? var mastoVersion: Version?
var hometownVersion: Version? var hometownVersion: Version?
// like "1.0.6+3.5.2"
let parts = ver.split(separator: "+") let parts = ver.split(separator: "+")
if parts.count == 2 { if parts.count == 2,
mastoVersion = Version(string: String(parts[1])) let first = Version(string: String(parts[0])) {
hometownVersion = Version(string: String(parts[0])) if first > Version(1, 0, 8) {
// like 3.5.5+hometown-1.0.9
mastoVersion = first
if parts[1].starts(with: "hometown-") {
hometownVersion = Version(string: String(parts[1][parts[1].index(parts[1].startIndex, offsetBy: "hometown-".count + 1)...]))
}
} else {
// like "1.0.6+3.5.2"
hometownVersion = first
mastoVersion = Version(string: String(parts[1]))
}
} else { } else {
mastoVersion = Version(string: ver) mastoVersion = Version(string: ver)
} }

View File

@ -125,6 +125,8 @@ class Preferences: Codable, ObservableObject {
@Published var showIsStatusReplyIcon = false @Published var showIsStatusReplyIcon = false
@Published var alwaysShowStatusVisibilityIcon = false @Published var alwaysShowStatusVisibilityIcon = false
@Published var hideActionsInTimeline = false @Published var hideActionsInTimeline = false
@Published var leadingStatusSwipeActions: [StatusSwipeAction] = [.favorite, .reblog]
@Published var trailingStatusSwipeActions: [StatusSwipeAction] = [.reply, .share]
// MARK: Composing // MARK: Composing
@Published var defaultPostVisibility = Status.Visibility.public @Published var defaultPostVisibility = Status.Visibility.public

View File

@ -0,0 +1,172 @@
//
// StatusSwipeAction.swift
// Tusker
//
// Created by Shadowfacts on 11/26/22.
// Copyright © 2022 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
enum StatusSwipeAction: String, Codable, Hashable, CaseIterable {
case reply
case favorite
case reblog
case share
case bookmark
case openInSafari
var displayName: String {
switch self {
case .reply:
return "Reply"
case .favorite:
return "Favorite"
case .reblog:
return "Reblog"
case .share:
return "Share"
case .bookmark:
return "Bookmark"
case .openInSafari:
return "Open in Safari"
}
}
var systemImageName: String {
switch self {
case .reply:
return "arrowshape.turn.up.left.fill"
case .favorite:
return "star.fill"
case .reblog:
return "repeat"
case .share:
return "square.and.arrow.up"
case .bookmark:
return "bookmark.fill"
case .openInSafari:
return "safari"
}
}
func createAction(status: StatusMO, container: StatusSwipeActionContainer) -> UIContextualAction? {
switch self {
case .reply:
return createReplyAction(status: status, container: container)
case .favorite:
return createFavoriteAction(status: status, container: container)
case .reblog:
return createReblogAction(status: status, container: container)
case .share:
return createShareAction(status: status, container: container)
case .bookmark:
return createBookmarkAction(status: status, container: container)
case .openInSafari:
return createOpenInSafariAction(status: status, container: container)
}
}
}
protocol StatusSwipeActionContainer: UIView {
var mastodonController: MastodonController! { get }
var navigationDelegate: any TuskerNavigationDelegate { get }
var toastableViewController: ToastableViewController? { get }
// necessary b/c the reblog-handling logic only exists in the cells
func performReplyAction()
}
private func createReplyAction(status: StatusMO, container: StatusSwipeActionContainer) -> UIContextualAction? {
guard container.mastodonController.loggedIn else {
return nil
}
let action = UIContextualAction(style: .normal, title: "Reply") { [unowned container] _, _, completion in
container.performReplyAction()
completion(true)
}
action.image = UIImage(systemName: "arrowshape.turn.up.left.fill")
action.backgroundColor = container.tintColor
return action
}
private func createFavoriteAction(status: StatusMO, container: StatusSwipeActionContainer) -> UIContextualAction? {
guard container.mastodonController.loggedIn else {
return nil
}
let title = status.favourited ? "Unfavorite" : "Favorite"
let action = UIContextualAction(style: .normal, title: title) { [unowned container] _, _, completion in
Task {
await FavoriteService(status: status, mastodonController: container.mastodonController, presenter: container.navigationDelegate).toggleFavorite()
completion(true)
}
}
action.image = UIImage(systemName: "star.fill")
action.backgroundColor = status.favourited ? UIColor(displayP3Red: 235/255, green: 77/255, blue: 62/255, alpha: 1) : UIColor(displayP3Red: 1, green: 204/255, blue: 0, alpha: 1)
return action
}
private func createReblogAction(status: StatusMO, container: StatusSwipeActionContainer) -> UIContextualAction? {
guard container.mastodonController.loggedIn else {
return nil
}
let title = status.reblogged ? "Unreblog" : "Reblog"
let action = UIContextualAction(style: .normal, title: title) { [unowned container] _, _, completion in
Task {
await ReblogService(status: status, mastodonController: container.mastodonController, presenter: container.navigationDelegate).toggleReblog()
completion(true)
}
}
action.image = UIImage(systemName: "repeat")
action.backgroundColor = status.reblogged ? UIColor(displayP3Red: 235/255, green: 77/255, blue: 62/255, alpha: 1) : container.tintColor
return action
}
private func createShareAction(status: StatusMO, container: StatusSwipeActionContainer) -> UIContextualAction {
let action = UIContextualAction(style: .normal, title: "Share") { [unowned container] _, _, completion in
container.navigationDelegate.showMoreOptions(forStatus: status.id, sourceView: container)
completion(true)
}
// bold to more closesly match other action symbols
let config = UIImage.SymbolConfiguration(weight: .bold)
action.image = UIImage(systemName: "square.and.arrow.up")!.applyingSymbolConfiguration(config)!
action.backgroundColor = .lightGray
return action
}
private func createBookmarkAction(status: StatusMO, container: StatusSwipeActionContainer) -> UIContextualAction? {
guard container.mastodonController.loggedIn else {
return nil
}
let bookmarked = status.bookmarked ?? false
let title = bookmarked ? "Unbookmark" : "Bookmark"
let action = UIContextualAction(style: .normal, title: title) { [unowned container] _, _, completion in
Task { @MainActor in
let request = (bookmarked ? Status.unbookmark : Status.bookmark)(status.id)
do {
let (status, _) = try await container.mastodonController.run(request)
container.mastodonController.persistentContainer.addOrUpdate(status: status)
} catch {
if let toastable = container.toastableViewController {
let config = ToastConfiguration(from: error, with: "Error \(bookmarked ? "Unb" : "B")ookmarking", in: toastable, retryAction: nil)
toastable.showToast(configuration: config, animated: true)
}
}
completion(true)
}
}
action.image = UIImage(systemName: "bookmark.fill")
action.backgroundColor = .systemRed
return action
}
private func createOpenInSafariAction(status: StatusMO, container: StatusSwipeActionContainer) -> UIContextualAction {
let action = UIContextualAction(style: .normal, title: "Open in Safari") { [unowned container] _, _, completion in
container.navigationDelegate.selected(url: status.url!, allowUniversalLinks: false)
completion(true)
}
action.image = UIImage(systemName: "safari")
action.backgroundColor = container.tintColor
return action
}

View File

@ -59,6 +59,16 @@ struct AppearancePrefsView : View {
Toggle(isOn: $preferences.hideActionsInTimeline) { Toggle(isOn: $preferences.hideActionsInTimeline) {
Text("Hide Actions on Timeline") Text("Hide Actions on Timeline")
} }
NavigationLink("Leading Swipe Actions") {
SwipeActionsPrefsView(selection: $preferences.leadingStatusSwipeActions)
.edgesIgnoringSafeArea(.all)
.navigationTitle("Leading Swipe Actions")
}
NavigationLink("Trailing Swipe Actions") {
SwipeActionsPrefsView(selection: $preferences.trailingStatusSwipeActions)
.edgesIgnoringSafeArea(.all)
.navigationTitle("Trailing Swipe Actions")
}
} }
} }
} }

View File

@ -0,0 +1,122 @@
//
// SwipeActionsPrefsView.swift
// Tusker
//
// Created by Shadowfacts on 11/26/22.
// Copyright © 2022 Shadowfacts. All rights reserved.
//
import SwiftUI
struct SwipeActionsPrefsView: UIViewControllerRepresentable {
@Binding var selection: [StatusSwipeAction]
typealias UIViewControllerType = SwipeActionsPrefsViewController
func makeUIViewController(context: Context) -> SwipeActionsPrefsViewController {
return SwipeActionsPrefsViewController(selection: $selection)
}
func updateUIViewController(_ uiViewController: SwipeActionsPrefsViewController, context: Context) {
}
}
class SwipeActionsPrefsViewController: UIViewController, UICollectionViewDelegate {
@Binding var selection: [StatusSwipeAction]
private var collectionView: UICollectionView {
view as! UICollectionView
}
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
init(selection: Binding<[StatusSwipeAction]>) {
self._selection = selection
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
let layout = UICollectionViewCompositionalLayout { [unowned self] sectionIndex, environment in
var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
if dataSource.sectionIdentifier(for: sectionIndex) == .selected {
config.headerMode = .supplementary
}
return .list(using: config, layoutEnvironment: environment)
}
view = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self
dataSource = createDataSource()
}
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
let listCell = UICollectionView.CellRegistration<UICollectionViewListCell, StatusSwipeAction> { cell, indexPath, item in
var config = cell.defaultContentConfiguration()
config.text = item.displayName
config.image = UIImage(systemName: item.systemImageName)
cell.contentConfiguration = config
cell.accessories = [.reorder(displayed: .always)]
}
let dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
return collectionView.dequeueConfiguredReusableCell(using: listCell, for: indexPath, item: itemIdentifier)
}
let headerCell = UICollectionView.SupplementaryRegistration<UICollectionViewListCell>(elementKind: UICollectionView.elementKindSectionHeader) { supplementaryView, elementKind, indexPath in
var config = supplementaryView.defaultContentConfiguration()
config.text = "Selected"
supplementaryView.contentConfiguration = config
}
dataSource.supplementaryViewProvider = { collectionView, kind, indexPath in
return collectionView.dequeueConfiguredReusableSupplementary(using: headerCell, for: indexPath)
}
dataSource.reorderingHandlers.canReorderItem = { _ in
return true
}
dataSource.reorderingHandlers.didReorder = { [unowned self] transaction in
guard let selectedSection = transaction.sectionTransactions.first(where: { $0.sectionIdentifier == .selected }) else {
return
}
self.selection = self.selection.applying(selectedSection.difference)!
}
return dataSource
}
override func viewDidLoad() {
super.viewDidLoad()
setEditing(true, animated: false)
applySnapshot(animated: false)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
guard let item = dataSource.itemIdentifier(for: indexPath),
let section = dataSource.sectionIdentifier(for: indexPath.section) else {
return
}
switch section {
case .selected:
selection.removeAll(where: { $0 == item })
case .remainder:
selection.append(item)
}
applySnapshot(animated: true)
}
private func applySnapshot(animated: Bool) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.selected, .remainder])
snapshot.appendItems(selection, toSection: .selected)
snapshot.appendItems(StatusSwipeAction.allCases.filter { !selection.contains($0) }, toSection: .remainder)
dataSource.apply(snapshot, animatingDifferences: animated)
}
enum Section {
case selected
case remainder
}
typealias Item = StatusSwipeAction
}

View File

@ -578,58 +578,17 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
} }
func leadingSwipeActions() -> UISwipeActionsConfiguration? { func leadingSwipeActions() -> UISwipeActionsConfiguration? {
guard mastodonController.loggedIn, guard let status = mastodonController.persistentContainer.status(for: statusID) else {
let status = mastodonController.persistentContainer.status(for: statusID) else {
return nil return nil
} }
return UISwipeActionsConfiguration(actions: Preferences.shared.leadingStatusSwipeActions.compactMap { $0.createAction(status: status, container: self) })
let favoriteTitle = status.favourited ? "Unfavorite" : "Favorite"
let favorite = UIContextualAction(style: .normal, title: favoriteTitle) { [unowned self] _, _, completion in
Task {
await FavoriteService(status: status, mastodonController: self.mastodonController, presenter: self.delegate!).toggleFavorite()
completion(true)
}
}
favorite.image = UIImage(systemName: "star.fill")
favorite.backgroundColor = status.favourited ? UIColor(displayP3Red: 235/255, green: 77/255, blue: 62/255, alpha: 1) : UIColor(displayP3Red: 1, green: 204/255, blue: 0, alpha: 1)
let reblogTitle = status.reblogged ? "Unreblog" : "Reblog"
let reblog = UIContextualAction(style: .normal, title: reblogTitle) { _, _, completion in
Task {
await ReblogService(status: status, mastodonController: self.mastodonController, presenter: self.delegate!).toggleReblog()
completion(true)
}
}
reblog.image = UIImage(systemName: "repeat")
reblog.backgroundColor = status.reblogged ? UIColor(displayP3Red: 235/255, green: 77/255, blue: 62/255, alpha: 1) : tintColor
return UISwipeActionsConfiguration(actions: [favorite, reblog])
} }
func trailingSwipeActions() -> UISwipeActionsConfiguration? { func trailingSwipeActions() -> UISwipeActionsConfiguration? {
var actions = [UIContextualAction]() guard let status = mastodonController.persistentContainer.status(for: statusID) else {
return nil
let share = UIContextualAction(style: .normal, title: "Share") { [unowned self] _, _, completion in
self.delegate?.showMoreOptions(forStatus: statusID, sourceView: self)
completion(true)
} }
// bold to more closesly match other action symbols return UISwipeActionsConfiguration(actions: Preferences.shared.trailingStatusSwipeActions.compactMap { $0.createAction(status: status, container: self) })
let config = UIImage.SymbolConfiguration(weight: .bold)
share.image = UIImage(systemName: "square.and.arrow.up")!.applyingSymbolConfiguration(config)!
share.backgroundColor = .lightGray
actions.append(share)
if mastodonController.loggedIn {
let reply = UIContextualAction(style: .normal, title: "Reply") { [unowned self] _, _, completion in
self.replyPressed()
completion(true)
}
reply.image = UIImage(systemName: "arrowshape.turn.up.left.fill")
reply.backgroundColor = tintColor
actions.insert(reply, at: 0)
}
return UISwipeActionsConfiguration(actions: actions)
} }
func dragItemsForBeginning(session: UIDragSession) -> [UIDragItem] { func dragItemsForBeginning(session: UIDragSession) -> [UIDragItem] {
@ -705,3 +664,12 @@ extension TimelineStatusCollectionViewCell: UIPointerInteractionDelegate {
return nil return nil
} }
} }
extension TimelineStatusCollectionViewCell: StatusSwipeActionContainer {
var navigationDelegate: TuskerNavigationDelegate { delegate! }
var toastableViewController: ToastableViewController? { delegate }
func performReplyAction() {
self.replyPressed()
}
}

View File

@ -319,90 +319,17 @@ extension TimelineStatusTableViewCell: SelectableTableViewCell {
extension TimelineStatusTableViewCell: TableViewSwipeActionProvider { extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
func leadingSwipeActionsConfiguration() -> UISwipeActionsConfiguration? { func leadingSwipeActionsConfiguration() -> UISwipeActionsConfiguration? {
guard let mastodonController = mastodonController, guard let status = mastodonController.persistentContainer.status(for: statusID) else {
mastodonController.loggedIn else { return nil } return nil
guard let status = mastodonController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
let favoriteTitle: String
let favoriteRequest: Request<Status>
let favoriteColor: UIColor
if status.favourited {
favoriteTitle = "Unfavorite"
favoriteRequest = Status.unfavourite(status.id)
favoriteColor = UIColor(displayP3Red: 235/255, green: 77/255, blue: 62/255, alpha: 1)
} else {
favoriteTitle = "Favorite"
favoriteRequest = Status.favourite(status.id)
favoriteColor = UIColor(displayP3Red: 1, green: 204/255, blue: 0, alpha: 1)
} }
let favorite = UIContextualAction(style: .normal, title: favoriteTitle) { (action, view, completion) in return UISwipeActionsConfiguration(actions: Preferences.shared.leadingStatusSwipeActions.compactMap { $0.createAction(status: status, container: self) })
mastodonController.run(favoriteRequest, completion: { response in
DispatchQueue.main.async {
guard case let .success(status, _) = response else {
completion(false)
return
}
completion(true)
mastodonController.persistentContainer.addOrUpdate(status: status)
}
})
}
favorite.image = UIImage(systemName: "star.fill")
favorite.backgroundColor = favoriteColor
let reblogTitle: String
let reblogRequest: Request<Status>
let reblogColor: UIColor
if status.reblogged {
reblogTitle = "Unreblog"
reblogRequest = Status.unreblog(status.id)
reblogColor = UIColor(displayP3Red: 235/255, green: 77/255, blue: 62/255, alpha: 1)
} else {
reblogTitle = "Reblog"
reblogRequest = Status.reblog(status.id)
reblogColor = tintColor
}
let reblog = UIContextualAction(style: .normal, title: reblogTitle) { (action, view, completion) in
mastodonController.run(reblogRequest, completion: { response in
DispatchQueue.main.async {
guard case let .success(status, _) = response else {
completion(false)
return
}
completion(true)
mastodonController.persistentContainer.addOrUpdate(status: status)
}
})
}
reblog.image = UIImage(systemName: "repeat")
reblog.backgroundColor = reblogColor
return UISwipeActionsConfiguration(actions: [favorite, reblog])
} }
func trailingSwipeActionsConfiguration() -> UISwipeActionsConfiguration? { func trailingSwipeActionsConfiguration() -> UISwipeActionsConfiguration? {
let share = UIContextualAction(style: .normal, title: "Share") { (action, view, completion) in guard let status = mastodonController.persistentContainer.status(for: statusID) else {
completion(true) return nil
self.delegate?.showMoreOptions(forStatus: self.statusID, sourceView: self)
} }
// Bold to more closely match the other action symbols return UISwipeActionsConfiguration(actions: Preferences.shared.trailingStatusSwipeActions.compactMap { $0.createAction(status: status, container: self) })
let config = UIImage.SymbolConfiguration(weight: .bold)
share.image = UIImage(systemName: "square.and.arrow.up")!.applyingSymbolConfiguration(config)!
share.backgroundColor = .lightGray
guard mastodonController.loggedIn else {
return UISwipeActionsConfiguration(actions: [share])
}
let reply = UIContextualAction(style: .normal, title: "Reply") { (action, view, completion) in
completion(true)
self.reply()
}
reply.image = UIImage(systemName: "arrowshape.turn.up.left.fill")
reply.backgroundColor = tintColor
return UISwipeActionsConfiguration(actions: [reply, share])
} }
} }
@ -461,3 +388,12 @@ extension TimelineStatusTableViewCell: MenuPreviewProvider {
) )
} }
} }
extension TimelineStatusTableViewCell: StatusSwipeActionContainer {
var navigationDelegate: TuskerNavigationDelegate { delegate! }
var toastableViewController: ToastableViewController? { delegate }
func performReplyAction() {
self.replyPressed()
}
}