Compare commits
No commits in common. "b7a4f7e30f3006873cca1357c79da462a4561b02" and "46db70d58ba29f4caef9be29d5c9ecf4ad9f5db3" have entirely different histories.
b7a4f7e30f
...
46db70d58b
|
@ -38,14 +38,12 @@
|
||||||
D61A45EA28DF51EE002BE511 /* TimelineLikeCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61A45E928DF51EE002BE511 /* TimelineLikeCollectionViewController.swift */; };
|
D61A45EA28DF51EE002BE511 /* TimelineLikeCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61A45E928DF51EE002BE511 /* TimelineLikeCollectionViewController.swift */; };
|
||||||
D61ABEF628EE74D400B29151 /* StatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61ABEF528EE74D400B29151 /* StatusCollectionViewCell.swift */; };
|
D61ABEF628EE74D400B29151 /* StatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61ABEF528EE74D400B29151 /* StatusCollectionViewCell.swift */; };
|
||||||
D61ABEFC28F105DE00B29151 /* Pachyderm in Frameworks */ = {isa = PBXBuildFile; productRef = D61ABEFB28F105DE00B29151 /* Pachyderm */; };
|
D61ABEFC28F105DE00B29151 /* Pachyderm in Frameworks */ = {isa = PBXBuildFile; productRef = D61ABEFB28F105DE00B29151 /* Pachyderm */; };
|
||||||
D61ABEFE28F1C92600B29151 /* FavoriteService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61ABEFD28F1C92600B29151 /* FavoriteService.swift */; };
|
|
||||||
D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */; };
|
D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */; };
|
||||||
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; };
|
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; };
|
||||||
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; };
|
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; };
|
||||||
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 */; };
|
||||||
D621733328F1D5ED004C7DB1 /* ReblogService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D621733228F1D5ED004C7DB1 /* ReblogService.swift */; };
|
|
||||||
D622757424EDF1CD00B82A16 /* ComposeAttachmentsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D622757324EDF1CD00B82A16 /* ComposeAttachmentsList.swift */; };
|
D622757424EDF1CD00B82A16 /* ComposeAttachmentsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D622757324EDF1CD00B82A16 /* ComposeAttachmentsList.swift */; };
|
||||||
D622757824EE133700B82A16 /* ComposeAssetPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D622757724EE133700B82A16 /* ComposeAssetPicker.swift */; };
|
D622757824EE133700B82A16 /* ComposeAssetPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D622757724EE133700B82A16 /* ComposeAssetPicker.swift */; };
|
||||||
D622757A24EE21D900B82A16 /* ComposeAttachmentRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D622757924EE21D900B82A16 /* ComposeAttachmentRow.swift */; };
|
D622757A24EE21D900B82A16 /* ComposeAttachmentRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D622757924EE21D900B82A16 /* ComposeAttachmentRow.swift */; };
|
||||||
|
@ -390,14 +388,12 @@
|
||||||
D61A45E728DF477D002BE511 /* LoadingCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingCollectionViewCell.swift; sourceTree = "<group>"; };
|
D61A45E728DF477D002BE511 /* LoadingCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
D61A45E928DF51EE002BE511 /* TimelineLikeCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineLikeCollectionViewController.swift; sourceTree = "<group>"; };
|
D61A45E928DF51EE002BE511 /* TimelineLikeCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineLikeCollectionViewController.swift; sourceTree = "<group>"; };
|
||||||
D61ABEF528EE74D400B29151 /* StatusCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusCollectionViewCell.swift; sourceTree = "<group>"; };
|
D61ABEF528EE74D400B29151 /* StatusCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
D61ABEFD28F1C92600B29151 /* FavoriteService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteService.swift; sourceTree = "<group>"; };
|
|
||||||
D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSelectorTableViewController.swift; sourceTree = "<group>"; };
|
D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSelectorTableViewController.swift; sourceTree = "<group>"; };
|
||||||
D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceTableViewCell.swift; sourceTree = "<group>"; };
|
D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InstanceTableViewCell.xib; sourceTree = "<group>"; };
|
D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InstanceTableViewCell.xib; 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>"; };
|
||||||
D621733228F1D5ED004C7DB1 /* ReblogService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReblogService.swift; sourceTree = "<group>"; };
|
|
||||||
D622757324EDF1CD00B82A16 /* ComposeAttachmentsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeAttachmentsList.swift; sourceTree = "<group>"; };
|
D622757324EDF1CD00B82A16 /* ComposeAttachmentsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeAttachmentsList.swift; sourceTree = "<group>"; };
|
||||||
D622757724EE133700B82A16 /* ComposeAssetPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeAssetPicker.swift; sourceTree = "<group>"; };
|
D622757724EE133700B82A16 /* ComposeAssetPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeAssetPicker.swift; sourceTree = "<group>"; };
|
||||||
D622757924EE21D900B82A16 /* ComposeAttachmentRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeAttachmentRow.swift; sourceTree = "<group>"; };
|
D622757924EE21D900B82A16 /* ComposeAttachmentRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeAttachmentRow.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1479,8 +1475,6 @@
|
||||||
D62E9988279DB2D100C26176 /* InstanceFeatures.swift */,
|
D62E9988279DB2D100C26176 /* InstanceFeatures.swift */,
|
||||||
D6F953EF21251A2900CF0F2B /* MastodonController.swift */,
|
D6F953EF21251A2900CF0F2B /* MastodonController.swift */,
|
||||||
D6E9CDA7281A427800BBC98E /* PostService.swift */,
|
D6E9CDA7281A427800BBC98E /* PostService.swift */,
|
||||||
D61ABEFD28F1C92600B29151 /* FavoriteService.swift */,
|
|
||||||
D621733228F1D5ED004C7DB1 /* ReblogService.swift */,
|
|
||||||
);
|
);
|
||||||
path = API;
|
path = API;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1902,7 +1896,6 @@
|
||||||
D681E4D3246E2AFF0053414F /* MuteConversationActivity.swift in Sources */,
|
D681E4D3246E2AFF0053414F /* MuteConversationActivity.swift in Sources */,
|
||||||
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */,
|
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */,
|
||||||
D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */,
|
D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */,
|
||||||
D61ABEFE28F1C92600B29151 /* FavoriteService.swift in Sources */,
|
|
||||||
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */,
|
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */,
|
||||||
D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */,
|
D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */,
|
||||||
D627943B23A55BA600D38C68 /* NavigableTableViewCell.swift in Sources */,
|
D627943B23A55BA600D38C68 /* NavigableTableViewCell.swift in Sources */,
|
||||||
|
@ -1954,7 +1947,6 @@
|
||||||
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */,
|
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */,
|
||||||
D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */,
|
D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */,
|
||||||
D693A72825CF282E003A14E2 /* TrendingHashtagsViewController.swift in Sources */,
|
D693A72825CF282E003A14E2 /* TrendingHashtagsViewController.swift in Sources */,
|
||||||
D621733328F1D5ED004C7DB1 /* ReblogService.swift in Sources */,
|
|
||||||
D62275A824F1CA2800B82A16 /* ComposeReplyContentView.swift in Sources */,
|
D62275A824F1CA2800B82A16 /* ComposeReplyContentView.swift in Sources */,
|
||||||
D677284824ECBCB100C732D3 /* ComposeView.swift in Sources */,
|
D677284824ECBCB100C732D3 /* ComposeView.swift in Sources */,
|
||||||
D68232F72464F4FD00325FB8 /* ComposeDrawingViewController.swift in Sources */,
|
D68232F72464F4FD00325FB8 /* ComposeDrawingViewController.swift in Sources */,
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
//
|
|
||||||
// FavoriteService.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 10/8/22.
|
|
||||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Pachyderm
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
class FavoriteService {
|
|
||||||
|
|
||||||
private let mastodonController: MastodonController
|
|
||||||
private let presenter: any TuskerNavigationDelegate
|
|
||||||
private let status: StatusMO
|
|
||||||
|
|
||||||
var hapticFeedback = true
|
|
||||||
|
|
||||||
init(status: StatusMO, mastodonController: MastodonController, presenter: any TuskerNavigationDelegate) {
|
|
||||||
self.status = status
|
|
||||||
self.mastodonController = mastodonController
|
|
||||||
self.presenter = presenter
|
|
||||||
}
|
|
||||||
|
|
||||||
func toggleFavorite() async {
|
|
||||||
let oldValue = status.favourited
|
|
||||||
status.favourited.toggle()
|
|
||||||
mastodonController.persistentContainer.statusSubject.send(status.id)
|
|
||||||
|
|
||||||
if hapticFeedback {
|
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
|
||||||
}
|
|
||||||
|
|
||||||
let request = (status.favourited ? Status.favourite : Status.unfavourite)(status.id)
|
|
||||||
do {
|
|
||||||
let (newStatus, _) = try await mastodonController.run(request)
|
|
||||||
mastodonController.persistentContainer.addOrUpdate(status: newStatus)
|
|
||||||
} catch {
|
|
||||||
status.favourited = oldValue
|
|
||||||
mastodonController.persistentContainer.statusSubject.send(status.id)
|
|
||||||
|
|
||||||
let title = oldValue ? "Error Unfavoriting" : "Error Favoriting"
|
|
||||||
let config = ToastConfiguration(from: error, with: title, in: presenter) { toast in
|
|
||||||
// deliberately retain a strong reference to self
|
|
||||||
toast.dismissToast(animated: true)
|
|
||||||
await self.toggleFavorite()
|
|
||||||
}
|
|
||||||
presenter.showToast(configuration: config, animated: true)
|
|
||||||
|
|
||||||
if hapticFeedback {
|
|
||||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
//
|
|
||||||
// ReblogService.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 10/8/22.
|
|
||||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Pachyderm
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
class ReblogService {
|
|
||||||
|
|
||||||
private let mastodonController: MastodonController
|
|
||||||
private let presenter: any TuskerNavigationDelegate
|
|
||||||
private let status: StatusMO
|
|
||||||
|
|
||||||
var hapticFeedback = true
|
|
||||||
var visibility: Status.Visibility? = nil
|
|
||||||
var requireConfirmation = Preferences.shared.confirmBeforeReblog
|
|
||||||
|
|
||||||
init(status: StatusMO, mastodonController: MastodonController, presenter: any TuskerNavigationDelegate) {
|
|
||||||
self.status = status
|
|
||||||
self.mastodonController = mastodonController
|
|
||||||
self.presenter = presenter
|
|
||||||
}
|
|
||||||
|
|
||||||
func toggleReblog() async {
|
|
||||||
if !status.reblogged,
|
|
||||||
requireConfirmation {
|
|
||||||
presentConfirmationAlert()
|
|
||||||
} else {
|
|
||||||
await doToggleReblog()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func presentConfirmationAlert() {
|
|
||||||
let image: UIImage?
|
|
||||||
let reblogVisibilityActions: [CustomAlertController.MenuAction]?
|
|
||||||
if mastodonController.instanceFeatures.reblogVisibility {
|
|
||||||
image = UIImage(systemName: Status.Visibility.public.unfilledImageName)
|
|
||||||
reblogVisibilityActions = [Status.Visibility.unlisted, .private].map { visibility in
|
|
||||||
CustomAlertController.MenuAction(title: "Reblog as \(visibility.displayName)", subtitle: visibility.subtitle, image: UIImage(systemName: visibility.unfilledImageName)) {
|
|
||||||
// deliberately retain a strong reference to self
|
|
||||||
Task {
|
|
||||||
await self.doToggleReblog()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
image = nil
|
|
||||||
reblogVisibilityActions = []
|
|
||||||
}
|
|
||||||
|
|
||||||
let preview = ConfirmReblogStatusPreviewView(status: status)
|
|
||||||
var config = CustomAlertController.Configuration(title: "Are you sure you want to reblog this post?", content: preview, actions: [
|
|
||||||
CustomAlertController.Action(title: "Cancel", style: .cancel, handler: nil),
|
|
||||||
CustomAlertController.Action(title: "Reblog", image: image, style: .default, handler: {
|
|
||||||
// deliberately retain a strong reference to self
|
|
||||||
Task {
|
|
||||||
await self.doToggleReblog()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
])
|
|
||||||
if let reblogVisibilityActions {
|
|
||||||
var menuAction = CustomAlertController.Action(title: nil, image: UIImage(systemName: "chevron.down"), style: .menu(reblogVisibilityActions), handler: nil)
|
|
||||||
menuAction.isSecondaryMenu = true
|
|
||||||
config.actions.append(menuAction)
|
|
||||||
}
|
|
||||||
let alert = CustomAlertController(config: config)
|
|
||||||
presenter.present(alert, animated: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func doToggleReblog() async {
|
|
||||||
let oldValue = status.reblogged
|
|
||||||
status.reblogged.toggle()
|
|
||||||
mastodonController.persistentContainer.statusSubject.send(status.id)
|
|
||||||
|
|
||||||
if hapticFeedback {
|
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
|
||||||
}
|
|
||||||
|
|
||||||
let request: Request<Status>
|
|
||||||
if status.reblogged {
|
|
||||||
request = Status.reblog(status.id, visibility: visibility)
|
|
||||||
} else {
|
|
||||||
request = Status.unreblog(status.id)
|
|
||||||
}
|
|
||||||
do {
|
|
||||||
let (newStatus, _) = try await mastodonController.run(request)
|
|
||||||
mastodonController.persistentContainer.addOrUpdate(status: newStatus)
|
|
||||||
} catch {
|
|
||||||
status.favourited = oldValue
|
|
||||||
mastodonController.persistentContainer.statusSubject.send(status.id)
|
|
||||||
|
|
||||||
let title = oldValue ? "Error Unfavoriting" : "Error Favoriting"
|
|
||||||
let config = ToastConfiguration(from: error, with: title, in: presenter) { toast in
|
|
||||||
toast.dismissToast(animated: true)
|
|
||||||
await self.toggleReblog()
|
|
||||||
}
|
|
||||||
presenter.showToast(configuration: config, animated: true)
|
|
||||||
|
|
||||||
if hapticFeedback {
|
|
||||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -12,7 +12,7 @@ import Combine
|
||||||
|
|
||||||
// TODO: gonna need a thing to replicate all of EnhancedTableViewController
|
// TODO: gonna need a thing to replicate all of EnhancedTableViewController
|
||||||
|
|
||||||
class TimelineViewController: UIViewController, TimelineLikeCollectionViewController, RefreshableViewController {
|
class TimelineViewController: UIViewController, TimelineLikeCollectionViewController {
|
||||||
let timeline: Timeline
|
let timeline: Timeline
|
||||||
weak var mastodonController: MastodonController!
|
weak var mastodonController: MastodonController!
|
||||||
|
|
||||||
|
@ -35,8 +35,6 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
|
||||||
self.controller = TimelineLikeController(delegate: self)
|
self.controller = TimelineLikeController(delegate: self)
|
||||||
|
|
||||||
addKeyCommand(MenuController.refreshCommand(discoverabilityTitle: "Refresh Timeline"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
|
@ -45,12 +43,8 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
|
|
||||||
override func loadView() {
|
override func loadView() {
|
||||||
var config = UICollectionLayoutListConfiguration(appearance: .plain)
|
var config = UICollectionLayoutListConfiguration(appearance: .plain)
|
||||||
config.leadingSwipeActionsConfigurationProvider = { [unowned self] in
|
// TODO: swipe actions
|
||||||
(collectionView.cellForItem(at: $0) as? TimelineStatusCollectionViewCell)?.leadingSwipeActions()
|
// config.trailingSwipeActionsConfigurationProvider =
|
||||||
}
|
|
||||||
config.trailingSwipeActionsConfigurationProvider = { [unowned self] in
|
|
||||||
(collectionView.cellForItem(at: $0) as? TimelineStatusCollectionViewCell)?.trailingSwipeActions()
|
|
||||||
}
|
|
||||||
config.itemSeparatorHandler = { [unowned self] indexPath, sectionSeparatorConfiguration in
|
config.itemSeparatorHandler = { [unowned self] indexPath, sectionSeparatorConfiguration in
|
||||||
guard let item = self.dataSource.itemIdentifier(for: indexPath) else {
|
guard let item = self.dataSource.itemIdentifier(for: indexPath) else {
|
||||||
return sectionSeparatorConfiguration
|
return sectionSeparatorConfiguration
|
||||||
|
@ -68,8 +62,9 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
}
|
}
|
||||||
let layout = UICollectionViewCompositionalLayout.list(using: config)
|
let layout = UICollectionViewCompositionalLayout.list(using: config)
|
||||||
view = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
view = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||||||
|
// TODO: delegates
|
||||||
collectionView.delegate = self
|
collectionView.delegate = self
|
||||||
collectionView.dragDelegate = self
|
// collectionView.dragDelegate = self
|
||||||
|
|
||||||
registerTimelineLikeCells()
|
registerTimelineLikeCells()
|
||||||
collectionView.register(PublicTimelineDescriptionCollectionViewCell.self, forCellWithReuseIdentifier: "publicTimelineDescription")
|
collectionView.register(PublicTimelineDescriptionCollectionViewCell.self, forCellWithReuseIdentifier: "publicTimelineDescription")
|
||||||
|
@ -77,13 +72,20 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
applyInitialSnapshot()
|
applyInitialSnapshot()
|
||||||
|
|
||||||
#if !targetEnvironment(macCatalyst)
|
#if !targetEnvironment(macCatalyst)
|
||||||
collectionView.refreshControl = UIRefreshControl()
|
let refreshControl = UIRefreshControl(frame: .zero, primaryAction: UIAction(handler: { [unowned self] _ in
|
||||||
collectionView.refreshControl!.addTarget(self, action: #selector(refresh), for: .valueChanged)
|
Task {
|
||||||
|
await self.controller.loadNewer()
|
||||||
|
self.collectionView.refreshControl!.endRefreshing()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
collectionView.refreshControl = refreshControl
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
// TODO: refresh key command
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
||||||
|
@ -151,15 +153,6 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
isShowingTimelineDescription = false
|
isShowingTimelineDescription = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func refresh() {
|
|
||||||
Task {
|
|
||||||
await controller.loadNewer()
|
|
||||||
#if !targetEnvironment(macCatalyst)
|
|
||||||
collectionView.refreshControl?.endRefreshing()
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TimelineViewController {
|
extension TimelineViewController {
|
||||||
|
@ -343,12 +336,6 @@ extension TimelineViewController: UICollectionViewDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TimelineViewController: UICollectionViewDragDelegate {
|
|
||||||
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
|
|
||||||
(collectionView.cellForItem(at: indexPath) as? TimelineStatusCollectionViewCell)?.dragItemsForBeginning(session: session) ?? []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension TimelineViewController: TuskerNavigationDelegate {
|
extension TimelineViewController: TuskerNavigationDelegate {
|
||||||
var apiController: MastodonController { mastodonController }
|
var apiController: MastodonController { mastodonController }
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import UIKit
|
||||||
import SafariServices
|
import SafariServices
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
|
||||||
protocol TuskerNavigationDelegate: UIViewController, ToastableViewController {
|
protocol TuskerNavigationDelegate: UIViewController {
|
||||||
var apiController: MastodonController { get }
|
var apiController: MastodonController { get }
|
||||||
|
|
||||||
func conversation(mainStatusID: String, state: StatusState) -> ConversationTableViewController
|
func conversation(mainStatusID: String, state: StatusState) -> ConversationTableViewController
|
||||||
|
|
|
@ -384,16 +384,93 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
@IBAction func favoritePressed() {
|
@IBAction func favoritePressed() {
|
||||||
guard let status = mastodonController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
guard let status = mastodonController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||||
|
|
||||||
Task {
|
let oldValue = favorited
|
||||||
await FavoriteService(status: status, mastodonController: mastodonController, presenter: delegate!).toggleFavorite()
|
favorited = !favorited
|
||||||
|
|
||||||
|
let realStatus = status.reblog ?? status
|
||||||
|
let request = (favorited ? Status.favourite : Status.unfavourite)(realStatus.id)
|
||||||
|
mastodonController.run(request) { response in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if case let .success(newStatus, _) = response {
|
||||||
|
self.favorited = newStatus.favourited ?? false
|
||||||
|
self.mastodonController.persistentContainer.addOrUpdate(status: newStatus)
|
||||||
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
|
} else {
|
||||||
|
self.favorited = oldValue
|
||||||
|
print("Couldn't favorite status \(realStatus.id)")
|
||||||
|
// todo: display error message
|
||||||
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func reblogPressed() {
|
@IBAction func reblogPressed() {
|
||||||
guard let status = mastodonController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
guard let status = mastodonController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||||
|
|
||||||
Task {
|
// if we are about to reblog and the user has confirmation enabled
|
||||||
await ReblogService(status: status, mastodonController: mastodonController, presenter: delegate!).toggleReblog()
|
if !reblogged,
|
||||||
|
Preferences.shared.confirmBeforeReblog {
|
||||||
|
let image: UIImage?
|
||||||
|
let reblogVisibilityActions: [CustomAlertController.MenuAction]?
|
||||||
|
if mastodonController.instanceFeatures.reblogVisibility {
|
||||||
|
image = UIImage(systemName: Status.Visibility.public.unfilledImageName)
|
||||||
|
reblogVisibilityActions = [Status.Visibility.unlisted, .private].map { visibility in
|
||||||
|
CustomAlertController.MenuAction(title: "Reblog as \(visibility.displayName)", subtitle: visibility.subtitle, image: UIImage(systemName: visibility.unfilledImageName)) { [unowned self] in
|
||||||
|
self.toggleReblogInternal(visibility: visibility)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
image = nil
|
||||||
|
reblogVisibilityActions = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let preview = ConfirmReblogStatusPreviewView(status: status)
|
||||||
|
var config = CustomAlertController.Configuration(title: "Are you sure you want to reblog this post?", content: preview, actions: [
|
||||||
|
CustomAlertController.Action(title: "Cancel", style: .cancel, handler: nil),
|
||||||
|
CustomAlertController.Action(title: "Reblog", image: image, style: .default, handler: { [unowned self] in
|
||||||
|
self.toggleReblogInternal(visibility: nil)
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
if let reblogVisibilityActions {
|
||||||
|
var menuAction = CustomAlertController.Action(title: nil, image: UIImage(systemName: "chevron.down"), style: .menu(reblogVisibilityActions), handler: nil)
|
||||||
|
menuAction.isSecondaryMenu = true
|
||||||
|
config.actions.append(menuAction)
|
||||||
|
}
|
||||||
|
let alert = CustomAlertController(config: config)
|
||||||
|
delegate?.present(alert, animated: true)
|
||||||
|
} else {
|
||||||
|
toggleReblogInternal(visibility: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func toggleReblogInternal(visibility: Status.Visibility?) {
|
||||||
|
guard let status = mastodonController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||||
|
|
||||||
|
let oldValue = reblogged
|
||||||
|
reblogged = !reblogged
|
||||||
|
|
||||||
|
let realStatus = status.reblog ?? status
|
||||||
|
let request: Request<Status>
|
||||||
|
if reblogged {
|
||||||
|
request = Status.reblog(realStatus.id, visibility: visibility)
|
||||||
|
} else {
|
||||||
|
request = Status.unreblog(realStatus.id)
|
||||||
|
}
|
||||||
|
mastodonController.run(request) { response in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if case let .success(newStatus, _) = response {
|
||||||
|
self.reblogged = newStatus.reblogged ?? false
|
||||||
|
self.mastodonController.persistentContainer.addOrUpdate(status: newStatus)
|
||||||
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
|
} else {
|
||||||
|
self.reblogged = oldValue
|
||||||
|
print("Couldn't reblog status \(realStatus.id)")
|
||||||
|
// todo: display error message
|
||||||
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ protocol StatusCollectionViewCell: UICollectionViewCell {
|
||||||
var displayNameLabel: EmojiLabel { get }
|
var displayNameLabel: EmojiLabel { get }
|
||||||
var usernameLabel: UILabel { get }
|
var usernameLabel: UILabel { get }
|
||||||
var contentWarningLabel: EmojiLabel { get }
|
var contentWarningLabel: EmojiLabel { get }
|
||||||
var collapseButton: StatusCollapseButton { get }
|
var collapseButton: UIButton { get }
|
||||||
var contentContainer: StatusContentContainer { get }
|
var contentContainer: StatusContentContainer { get }
|
||||||
var replyButton: UIButton { get }
|
var replyButton: UIButton { get }
|
||||||
var favoriteButton: UIButton { get }
|
var favoriteButton: UIButton { get }
|
||||||
|
@ -75,8 +75,6 @@ extension StatusCollectionViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
func doUpdateUI(status: StatusMO) {
|
func doUpdateUI(status: StatusMO) {
|
||||||
precondition(delegate != nil, "StatusCollectionViewCell must have delegate")
|
|
||||||
|
|
||||||
statusID = status.id
|
statusID = status.id
|
||||||
accountID = status.account.id
|
accountID = status.account.id
|
||||||
|
|
||||||
|
@ -116,16 +114,15 @@ extension StatusCollectionViewCell {
|
||||||
statusState.collapsed = false
|
statusState.collapsed = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
collapseButton.isHidden = !statusState.collapsible!
|
|
||||||
contentContainer.setCollapsed(statusState.collapsed!)
|
contentContainer.setCollapsed(statusState.collapsed!)
|
||||||
if statusState.collapsed! {
|
if statusState.collapsed! {
|
||||||
contentContainer.alpha = 0
|
contentContainer.alpha = 0
|
||||||
// TODO: is this accessing the image view before the button's been laid out?
|
// TODO: is this accessing the image view before the button's been laid out?
|
||||||
collapseButton.imageView!.transform = CGAffineTransform(rotationAngle: 0)
|
collapseButton.imageView!.transform = CGAffineTransform(rotationAngle: .pi)
|
||||||
collapseButton.accessibilityLabel = NSLocalizedString("Expand Status", comment: "expand status button accessibility label")
|
collapseButton.accessibilityLabel = NSLocalizedString("Expand Status", comment: "expand status button accessibility label")
|
||||||
} else {
|
} else {
|
||||||
contentContainer.alpha = 1
|
contentContainer.alpha = 1
|
||||||
collapseButton.imageView!.transform = CGAffineTransform(rotationAngle: .pi)
|
collapseButton.imageView!.transform = CGAffineTransform(rotationAngle: 0)
|
||||||
collapseButton.accessibilityLabel = NSLocalizedString("Collapse Status", comment: "collapse status button accessibility label")
|
collapseButton.accessibilityLabel = NSLocalizedString("Collapse Status", comment: "collapse status button accessibility label")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,8 +186,23 @@ extension StatusCollectionViewCell {
|
||||||
guard let status = mastodonController.persistentContainer.status(for: statusID) else {
|
guard let status = mastodonController.persistentContainer.status(for: statusID) else {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
let oldValue = status.favourited
|
||||||
|
status.favourited.toggle()
|
||||||
|
// update ui before network request to make things appear speedy
|
||||||
|
updateStatusState(status: status)
|
||||||
|
|
||||||
|
let request = (status.favourited ? Status.favourite : Status.unfavourite)(statusID)
|
||||||
Task {
|
Task {
|
||||||
await FavoriteService(status: status, mastodonController: mastodonController, presenter: delegate!).toggleFavorite()
|
do {
|
||||||
|
let (newStatus, _) = try await mastodonController.run(request)
|
||||||
|
mastodonController.persistentContainer.addOrUpdate(status: newStatus)
|
||||||
|
// TODO: should this before the network request
|
||||||
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
|
} catch {
|
||||||
|
status.favourited = oldValue
|
||||||
|
// TODO: display error message
|
||||||
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,8 +210,64 @@ extension StatusCollectionViewCell {
|
||||||
guard let status = mastodonController.persistentContainer.status(for: statusID) else {
|
guard let status = mastodonController.persistentContainer.status(for: statusID) else {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !status.reblogged,
|
||||||
|
Preferences.shared.confirmBeforeReblog {
|
||||||
|
let image: UIImage?
|
||||||
|
let reblogVisibilityActions: [CustomAlertController.MenuAction]?
|
||||||
|
if mastodonController.instanceFeatures.reblogVisibility {
|
||||||
|
image = UIImage(systemName: Status.Visibility.public.unfilledImageName)
|
||||||
|
reblogVisibilityActions = [Status.Visibility.unlisted, .private].map { visibility in
|
||||||
|
CustomAlertController.MenuAction(title: "Reblog as \(visibility.displayName)", subtitle: visibility.subtitle, image: UIImage(systemName: visibility.unfilledImageName)) { [unowned self] in
|
||||||
|
self.doReblog(status: status, visibility: visibility)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
image = nil
|
||||||
|
reblogVisibilityActions = []
|
||||||
|
}
|
||||||
|
|
||||||
|
let preview = ConfirmReblogStatusPreviewView(status: status)
|
||||||
|
var config = CustomAlertController.Configuration(title: "Are you sure you want to reblog this post?", content: preview, actions: [
|
||||||
|
CustomAlertController.Action(title: "Cancel", style: .cancel, handler: nil),
|
||||||
|
CustomAlertController.Action(title: "Reblog", image: image, style: .default, handler: { [unowned self] in
|
||||||
|
self.doReblog(status: status, visibility: nil)
|
||||||
|
})
|
||||||
|
])
|
||||||
|
if let reblogVisibilityActions {
|
||||||
|
var menuAction = CustomAlertController.Action(title: nil, image: UIImage(systemName: "chevron.down"), style: .menu(reblogVisibilityActions), handler: nil)
|
||||||
|
menuAction.isSecondaryMenu = true
|
||||||
|
config.actions.append(menuAction)
|
||||||
|
}
|
||||||
|
let alert = CustomAlertController(config: config)
|
||||||
|
delegate?.present(alert, animated: true)
|
||||||
|
} else {
|
||||||
|
doReblog(status: status, visibility: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func doReblog(status: StatusMO, visibility: Status.Visibility?) {
|
||||||
|
let oldValue = status.reblogged
|
||||||
|
status.reblogged.toggle()
|
||||||
|
updateStatusState(status: status)
|
||||||
|
|
||||||
|
let request: Request<Status>
|
||||||
|
if status.reblogged {
|
||||||
|
request = Status.reblog(statusID, visibility: visibility)
|
||||||
|
} else {
|
||||||
|
request = Status.unreblog(statusID)
|
||||||
|
}
|
||||||
Task {
|
Task {
|
||||||
await ReblogService(status: status, mastodonController: mastodonController, presenter: delegate!).toggleReblog()
|
do {
|
||||||
|
let (newStatus, _) = try await mastodonController.run(request)
|
||||||
|
mastodonController.persistentContainer.addOrUpdate(status: newStatus)
|
||||||
|
// TODO: should this before the network request
|
||||||
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
|
} catch {
|
||||||
|
status.reblogged = oldValue
|
||||||
|
// TODO: display error message
|
||||||
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,11 +122,10 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
||||||
]), size: 0)
|
]), size: 0)
|
||||||
// this needs to have a higher priorty than the content container's zero height constraint
|
// this needs to have a higher priorty than the content container's zero height constraint
|
||||||
$0.setContentHuggingPriority(.defaultHigh, for: .vertical)
|
$0.setContentHuggingPriority(.defaultHigh, for: .vertical)
|
||||||
$0.isUserInteractionEnabled = true
|
|
||||||
$0.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(collapseButtonPressed)))
|
$0.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(collapseButtonPressed)))
|
||||||
}
|
}
|
||||||
|
|
||||||
private(set) lazy var collapseButton = StatusCollapseButton(configuration: {
|
private(set) lazy var collapseButton = UIButton(configuration: {
|
||||||
var config = UIButton.Configuration.filled()
|
var config = UIButton.Configuration.filled()
|
||||||
config.image = UIImage(systemName: "chevron.down")
|
config.image = UIImage(systemName: "chevron.down")
|
||||||
return config
|
return config
|
||||||
|
@ -169,6 +168,7 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
||||||
reblogButton.widthAnchor.constraint(equalTo: replyButton.widthAnchor),
|
reblogButton.widthAnchor.constraint(equalTo: replyButton.widthAnchor),
|
||||||
moreButton.widthAnchor.constraint(equalTo: replyButton.widthAnchor),
|
moreButton.widthAnchor.constraint(equalTo: replyButton.widthAnchor),
|
||||||
|
|
||||||
|
// TODO: gah
|
||||||
placeholderReplyButtonLeadingConstraint,
|
placeholderReplyButtonLeadingConstraint,
|
||||||
replyButton.topAnchor.constraint(equalTo: $0.topAnchor),
|
replyButton.topAnchor.constraint(equalTo: $0.topAnchor),
|
||||||
replyButton.bottomAnchor.constraint(equalTo: $0.bottomAnchor),
|
replyButton.bottomAnchor.constraint(equalTo: $0.bottomAnchor),
|
||||||
|
@ -509,76 +509,6 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
||||||
toggleReblog()
|
toggleReblog()
|
||||||
}
|
}
|
||||||
|
|
||||||
func leadingSwipeActions() -> UISwipeActionsConfiguration? {
|
|
||||||
guard mastodonController.loggedIn,
|
|
||||||
let status = mastodonController.persistentContainer.status(for: statusID) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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? {
|
|
||||||
var actions = [UIContextualAction]()
|
|
||||||
|
|
||||||
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
|
|
||||||
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] {
|
|
||||||
// the poll options view is tracking while the user is dragging between options
|
|
||||||
// while that's happening, don't initiate a drag
|
|
||||||
guard !pollView.isTracking,
|
|
||||||
let status = mastodonController.persistentContainer.status(for: statusID),
|
|
||||||
let accountID = mastodonController.accountInfo?.id else {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
let provider = NSItemProvider(object: status.url! as NSURL)
|
|
||||||
let activity = UserActivityManager.showConversationActivity(mainStatusID: status.id, accountID: accountID)
|
|
||||||
activity.displaysAuxiliaryScene = true
|
|
||||||
provider.registerObject(activity, visibility: .all)
|
|
||||||
return [UIDragItem(itemProvider: provider)]
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TimelineStatusCollectionViewCell: UIContextMenuInteractionDelegate {
|
extension TimelineStatusCollectionViewCell: UIContextMenuInteractionDelegate {
|
||||||
|
|
Loading…
Reference in New Issue