Compare commits

..

7 Commits

Author SHA1 Message Date
Shadowfacts 382decd7da
Fix search section titles 2019-12-16 22:23:12 -05:00
Shadowfacts 05d79d5d03
Use same nav delegate more options for context menu share sheet 2019-12-14 13:36:05 -05:00
Shadowfacts 4c0607af79
Add (un)bookmarking to status more options 2019-12-14 12:40:50 -05:00
Shadowfacts eb6cfba9aa
Fix tablel view cells being re-selected on aborted nav swipe back 2019-12-14 11:59:31 -05:00
Shadowfacts c26657bafa
Use synchronized MastodonCache to prevent race condition crashes 2019-12-14 11:31:14 -05:00
Shadowfacts 0c78af7d4f
Store in reply to status in drafts 2019-12-14 11:30:35 -05:00
Shadowfacts 681cdb8bb5
Fix automatically created drafts not being deleted after successful post
The newly created draft needs to be set to the compose VC's currentDraft
so that it gets removed after the status is successfully created.

Also, save the drafts to disk after saving a draft so that crashes don't
cause draft loss.
2019-11-28 22:26:37 -05:00
29 changed files with 485 additions and 195 deletions

View File

@ -35,6 +35,7 @@ public class Status: Decodable {
public let application: Application?
public let language: String?
public let pinned: Bool?
public let bookmarked: Bool?
public static func getContext(_ status: Status) -> Request<ConversationContext> {
return Request<ConversationContext>(method: .get, path: "/api/v1/statuses/\(status.id)/context")
@ -84,6 +85,14 @@ public class Status: Decodable {
return Request<Status>(method: .post, path: "/api/v1/statuses/\(status.id)/unpin")
}
public static func bookmark(_ status: Status) -> Request<Status> {
return Request<Status>(method: .post, path: "/api/v1/statuses/\(status.id)/bookmark")
}
public static func unbookmark(_ status: Status) -> Request<Status> {
return Request<Status>(method: .post, path: "/api/v1/statuses/\(status.id)/unbookmark")
}
public static func muteConversation(_ status: Status) -> Request<Status> {
return Request<Status>(method: .post, path: "/api/v1/statuses/\(status.id)/mute")
}
@ -118,6 +127,7 @@ public class Status: Decodable {
case application
case language
case pinned
case bookmarked
}
}

View File

@ -76,6 +76,11 @@
D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */; };
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; };
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; };
D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943123A5466600D38C68 /* SelectableTableViewCell.swift */; };
D627943523A5525100D38C68 /* StatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943423A5525100D38C68 /* StatusActivity.swift */; };
D627943723A552C200D38C68 /* BookmarkStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943623A552C200D38C68 /* BookmarkStatusActivity.swift */; };
D627943923A553B600D38C68 /* UnbookmarkStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943823A553B600D38C68 /* UnbookmarkStatusActivity.swift */; };
D627943B23A55BA600D38C68 /* NavigableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */; };
D627FF76217E923E00CC0648 /* DraftsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF75217E923E00CC0648 /* DraftsManager.swift */; };
D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627FF78217E950100CC0648 /* DraftsTableViewController.xib */; };
D627FF7B217E951500CC0648 /* DraftsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF7A217E951500CC0648 /* DraftsTableViewController.swift */; };
@ -339,6 +344,11 @@
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>"; };
D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InstanceTableViewCell.xib; sourceTree = "<group>"; };
D627943123A5466600D38C68 /* SelectableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableTableViewCell.swift; sourceTree = "<group>"; };
D627943423A5525100D38C68 /* StatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusActivity.swift; sourceTree = "<group>"; };
D627943623A552C200D38C68 /* BookmarkStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkStatusActivity.swift; sourceTree = "<group>"; };
D627943823A553B600D38C68 /* UnbookmarkStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnbookmarkStatusActivity.swift; sourceTree = "<group>"; };
D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigableTableViewCell.swift; sourceTree = "<group>"; };
D627FF75217E923E00CC0648 /* DraftsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsManager.swift; sourceTree = "<group>"; };
D627FF78217E950100CC0648 /* DraftsTableViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DraftsTableViewController.xib; sourceTree = "<group>"; };
D627FF7A217E951500CC0648 /* DraftsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsTableViewController.swift; sourceTree = "<group>"; };
@ -697,6 +707,16 @@
path = "Instance Cell";
sourceTree = "<group>";
};
D627943323A5523800D38C68 /* Status Activities */ = {
isa = PBXGroup;
children = (
D627943423A5525100D38C68 /* StatusActivity.swift */,
D627943623A552C200D38C68 /* BookmarkStatusActivity.swift */,
D627943823A553B600D38C68 /* UnbookmarkStatusActivity.swift */,
);
path = "Status Activities";
sourceTree = "<group>";
};
D627FF77217E94F200CC0648 /* Drafts */ = {
isa = PBXGroup;
children = (
@ -1041,8 +1061,9 @@
isa = PBXGroup;
children = (
D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */,
D6AEBB4623216B0C00E5038B /* Account Activities */,
D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */,
D6AEBB4623216B0C00E5038B /* Account Activities */,
D627943323A5523800D38C68 /* Status Activities */,
);
path = Activities;
sourceTree = "<group>";
@ -1074,6 +1095,8 @@
D6C693F82162E4DB007D6A6D /* StatusContentLabel.swift */,
D641C77E213DC78A004B4513 /* InlineTextAttachment.swift */,
04ED00B021481ED800567C53 /* SteppedProgressView.swift */,
D627943A23A55BA600D38C68 /* NavigableTableViewCell.swift */,
D627943123A5466600D38C68 /* SelectableTableViewCell.swift */,
D67C57A721E2649B00C3118B /* Account Detail */,
D67C57B021E28F9400C3118B /* Compose Status Reply */,
D60C07E221E817560057FAA8 /* Compose Media */,
@ -1577,6 +1600,7 @@
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */,
0411610022B442870030A9B7 /* AttachmentViewController.swift in Sources */,
D611C2CF232DC61100C86A49 /* HashtagTableViewCell.swift in Sources */,
D627943723A552C200D38C68 /* BookmarkStatusActivity.swift in Sources */,
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */,
D66362712136338600C9CBA2 /* ComposeViewController.swift in Sources */,
D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */,
@ -1603,6 +1627,7 @@
D6AEBB4A23216F0400E5038B /* UnfollowAccountActivity.swift in Sources */,
D663626421360D2300C9CBA2 /* AvatarStyle.swift in Sources */,
D679C09F215850EF00DA27FE /* XCBActions.swift in Sources */,
D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */,
D6DD353F22F502EC00A9563A /* Preferences+Notification.swift in Sources */,
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */,
D6AEBB4523216AF800E5038B /* FollowAccountActivity.swift in Sources */,
@ -1610,14 +1635,17 @@
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */,
D6BED174212667E900F02DA0 /* TimelineStatusTableViewCell.swift in Sources */,
0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */,
D627943923A553B600D38C68 /* UnbookmarkStatusActivity.swift in Sources */,
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */,
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
D6434EB3215B1856001A919A /* XCBRequest.swift in Sources */,
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */,
D627943B23A55BA600D38C68 /* NavigableTableViewCell.swift in Sources */,
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */,
D641C77F213DC78A004B4513 /* InlineTextAttachment.swift in Sources */,
D627943523A5525100D38C68 /* StatusActivity.swift in Sources */,
04496BD721625361001F1B23 /* ContentLabel.swift in Sources */,
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */,
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */,

View File

@ -0,0 +1,41 @@
//
// BookmarkStatusActivity.swift
// Tusker
//
// Created by Shadowfacts on 12/14/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
class BookmarkStatusActivity: StatusActivity {
override var activityType: UIActivity.ActivityType? {
return .bookmarkStatus
}
override var activityTitle: String? {
return NSLocalizedString("Bookmark", comment: "bookmark status activity title")
}
override var activityImage: UIImage? {
return UIImage(systemName: "bookmark")
}
override func perform() {
guard let status = status else { return }
let request = Status.bookmark(status)
MastodonController.client.run(request) { (response) in
if case let .success(status, _) = response {
MastodonCache.add(status: status)
} else {
// todo: display error message
UINotificationFeedbackGenerator().notificationOccurred(.error)
fatalError()
}
}
}
}

View File

@ -0,0 +1,34 @@
//
// StatusActivity.swift
// Tusker
//
// Created by Shadowfacts on 12/14/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
class StatusActivity: UIActivity {
override class var activityCategory: UIActivity.Category {
return .action
}
var status: Status?
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
for case is Status in activityItems {
return true
}
return false
}
override func prepare(withActivityItems activityItems: [Any]) {
for case let status as Status in activityItems {
self.status = status
return
}
}
}

View File

@ -0,0 +1,41 @@
//
// UnbookmarkStatusActivity.swift
// Tusker
//
// Created by Shadowfacts on 12/14/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
class UnbookmarkStatusActivity: StatusActivity {
override var activityType: UIActivity.ActivityType? {
return .unbookmarkStatus
}
override var activityTitle: String? {
return NSLocalizedString("Unbookmark", comment: "unbookmark status activity title")
}
override var activityImage: UIImage? {
return UIImage(systemName: "bookmark.fill")
}
override func perform() {
guard let status = status else { return }
let request = Status.unbookmark(status)
MastodonController.client.run(request) { (response) in
if case let .success(status, _) = response {
MastodonCache.add(status: status)
} else {
// todo: display error message
UINotificationFeedbackGenerator().notificationOccurred(.error)
fatalError()
}
}
}
}

View File

@ -18,5 +18,7 @@ extension UIActivity.ActivityType {
static let unfollowAccount = UIActivity.ActivityType("\(Bundle.main.bundleIdentifier!).unfollow_account")
// Status
static let bookmarkStatus = UIActivity.ActivityType("\(Bundle.main.bundleIdentifier!).bookmark_status")
static let unbookmarkStatus = UIActivity.ActivityType("\(Bundle.main.bundleIdentifier!).unbookmark_status")
}

View File

@ -16,9 +16,11 @@ class DraftsManager: Codable {
private static var archiveURL = DraftsManager.documentsDirectory.appendingPathComponent("drafts").appendingPathExtension("plist")
static func save() {
let encoder = PropertyListEncoder()
let data = try? encoder.encode(shared)
try? data?.write(to: archiveURL, options: .noFileProtection)
DispatchQueue.global(qos: .userInitiated).async {
let encoder = PropertyListEncoder()
let data = try? encoder.encode(shared)
try? data?.write(to: archiveURL, options: .noFileProtection)
}
}
static func load() -> DraftsManager {
@ -37,8 +39,10 @@ class DraftsManager: Codable {
return drafts.sorted(by: { $0.lastModified > $1.lastModified })
}
func create(text: String, contentWarning: String?, attachments: [DraftAttachment]) {
drafts.append(Draft(text: text, contentWarning: contentWarning, lastModified: Date(), attachments: attachments))
func create(text: String, contentWarning: String?, inReplyToID: String?, attachments: [DraftAttachment]) -> Draft {
let draft = Draft(text: text, contentWarning: contentWarning, inReplyToID: inReplyToID, attachments: attachments)
drafts.append(draft)
return draft
}
func remove(_ draft: Draft) {
@ -53,15 +57,17 @@ extension DraftsManager {
let id: UUID
private(set) var text: String
private(set) var contentWarning: String?
private(set) var lastModified: Date
private(set) var attachments: [DraftAttachment]
private(set) var inReplyToID: String?
private(set) var lastModified: Date
init(text: String, contentWarning: String?, lastModified: Date, attachments: [DraftAttachment]) {
init(text: String, contentWarning: String?, inReplyToID: String?, attachments: [DraftAttachment], lastModified: Date = Date()) {
self.id = UUID()
self.text = text
self.contentWarning = contentWarning
self.lastModified = lastModified
self.inReplyToID = inReplyToID
self.attachments = attachments
self.lastModified = lastModified
}
func update(text: String, contentWarning: String?, attachments: [DraftAttachment]) {

View File

@ -12,10 +12,10 @@ import Pachyderm
class MastodonCache {
private static var statuses = [String: Status]()
private static var accounts = [String: Account]()
private static var relationships = [String: Relationship]()
private static var notifications = [String: Pachyderm.Notification]()
private static var statuses = CachedDictionary<Status>(name: "Statuses")
private static var accounts = CachedDictionary<Account>(name: "Accounts")
private static var relationships = CachedDictionary<Relationship>(name: "Relationships")
private static var notifications = CachedDictionary<Pachyderm.Notification>(name: "Notifications")
static let statusSubject = PassthroughSubject<Status, Never>()
static let accountSubject = PassthroughSubject<Account, Never>()
@ -134,3 +134,29 @@ class MastodonCache {
}
}
class CachedDictionary<Value> {
private let name: String
private var dict = [String: Value]()
private let queue: DispatchQueue
init(name: String) {
self.name = name
self.queue = DispatchQueue(label: "CachedDictionary (\(name)) Coordinator", attributes: .concurrent)
}
subscript(key: String) -> Value? {
get {
var result: Value? = nil
queue.sync {
result = dict[key]
}
return result
}
set(value) {
queue.async(flags: .barrier) {
self.dict[key] = value
}
}
}
}

View File

@ -57,6 +57,8 @@ class ComposeViewController: UIViewController {
@IBOutlet weak var statusTextView: UITextView!
@IBOutlet weak var placeholderLabel: UILabel!
@IBOutlet weak var inReplyToContainer: UIView!
@IBOutlet weak var inReplyToLabel: UILabel!
@IBOutlet weak var contentWarningContainerView: UIView!
@IBOutlet weak var contentWarningTextField: UITextField!
@ -127,49 +129,7 @@ class ComposeViewController: UIViewController {
}
}
if let inReplyToID = inReplyToID, let inReplyTo = MastodonCache.status(for: inReplyToID) {
visibility = inReplyTo.visibility
if Preferences.shared.contentWarningCopyMode == .doNotCopy {
contentWarningEnabled = false
contentWarningContainerView.isHidden = true
} else {
contentWarningEnabled = !inReplyTo.spoilerText.isEmpty
contentWarningContainerView.isHidden = !contentWarningEnabled
if Preferences.shared.contentWarningCopyMode == .prependRe,
!inReplyTo.spoilerText.lowercased().starts(with: "re:") {
contentWarningTextField.text = "re: \(inReplyTo.spoilerText)"
} else {
contentWarningTextField.text = inReplyTo.spoilerText
}
}
let replyView = ComposeStatusReplyView.create()
replyView.updateUI(for: inReplyTo)
stackView.insertArrangedSubview(replyView, at: 0)
self.replyView = replyView
replyAvatarImageViewTopConstraint = replyView.avatarImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8)
replyAvatarImageViewTopConstraint!.isActive = true
let replyLabelContainer = UIView()
replyLabelContainer.backgroundColor = .clear
replyLabelContainer.translatesAutoresizingMaskIntoConstraints = false
let replyLabel = UILabel()
replyLabel.translatesAutoresizingMaskIntoConstraints = false
replyLabel.text = "In reply to \(inReplyTo.account.realDisplayName)"
replyLabel.textColor = .secondaryLabel
replyLabelContainer.addSubview(replyLabel)
NSLayoutConstraint.activate([
replyLabel.leadingAnchor.constraint(equalTo: replyLabelContainer.leadingAnchor, constant: 8),
replyLabel.trailingAnchor.constraint(equalTo: replyLabelContainer.trailingAnchor, constant: -8),
replyLabel.topAnchor.constraint(equalTo: replyLabelContainer.topAnchor),
replyLabel.bottomAnchor.constraint(equalTo: replyLabelContainer.bottomAnchor)
])
stackView.insertArrangedSubview(replyLabelContainer, at: 1)
}
updateInReplyTo()
// we have to set the font here, because the monospaced digit font is not available in IB
charactersRemainingLabel.font = .monospacedDigitSystemFont(ofSize: 17, weight: .regular)
@ -177,13 +137,65 @@ class ComposeViewController: UIViewController {
updatePlaceholder()
NotificationCenter.default.addObserver(self, selector: #selector(contentWarningTextFieldDidChange), name: UITextField.textDidChangeNotification, object: contentWarningTextField)
}
func updateInReplyTo() {
if let replyView = replyView {
replyView.removeFromSuperview()
}
if inReplyToID == nil {
if let inReplyToID = inReplyToID {
if let status = MastodonCache.status(for: inReplyToID) {
updateInReplyTo(inReplyTo: status)
} else {
let loadingVC = LoadingViewController()
embedChild(loadingVC)
MastodonCache.status(for: inReplyToID) { (status) in
guard let status = status else { return }
DispatchQueue.main.async {
self.updateInReplyTo(inReplyTo: status)
loadingVC.removeViewAndController()
}
}
}
} else {
visibility = Preferences.shared.defaultPostVisibility
contentWarningEnabled = false
inReplyToContainer.isHidden = true
}
}
func updateInReplyTo(inReplyTo: Status) {
visibility = inReplyTo.visibility
if Preferences.shared.contentWarningCopyMode == .doNotCopy {
contentWarningEnabled = false
contentWarningContainerView.isHidden = true
} else {
contentWarningEnabled = !inReplyTo.spoilerText.isEmpty
contentWarningContainerView.isHidden = !contentWarningEnabled
if Preferences.shared.contentWarningCopyMode == .prependRe,
!inReplyTo.spoilerText.lowercased().starts(with: "re:") {
contentWarningTextField.text = "re: \(inReplyTo.spoilerText)"
} else {
contentWarningTextField.text = inReplyTo.spoilerText
}
}
let replyView = ComposeStatusReplyView.create()
replyView.updateUI(for: inReplyTo)
stackView.insertArrangedSubview(replyView, at: 0)
self.replyView = replyView
replyAvatarImageViewTopConstraint = replyView.avatarImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8)
replyAvatarImageViewTopConstraint!.isActive = true
inReplyToContainer.isHidden = false
inReplyToLabel.text = "In reply to \(inReplyTo.account.realDisplayName)"
}
override func viewWillAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
@ -338,8 +350,9 @@ class ComposeViewController: UIViewController {
if let currentDraft = self.currentDraft {
currentDraft.update(text: self.statusTextView.text, contentWarning: cw, attachments: attachments)
} else {
DraftsManager.shared.create(text: self.statusTextView.text, contentWarning: cw, attachments: attachments)
self.currentDraft = DraftsManager.shared.create(text: self.statusTextView.text, contentWarning: cw, inReplyToID: inReplyToID, attachments: attachments)
}
DraftsManager.save()
}
@objc func close() {
@ -597,9 +610,30 @@ extension ComposeViewController: DraftsTableViewControllerDelegate {
func draftSelectionCanceled() {
}
func shouldSelectDraft(_ draft: DraftsManager.Draft, completion: @escaping (Bool) -> Void) {
if draft.inReplyToID != self.inReplyToID {
// todo: better text for this
let alertController = UIAlertController(title: "Different Reply", message: "The selected draft is a reply to a different status, do you wish to use it?", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in
completion(false)
}))
alertController.addAction(UIAlertAction(title: "Restore Draft", style: .default, handler: { (_) in
completion(true)
}))
// we can't present the alert ourselves, since the compose VC is already presenting the draft selector
// but presenting on the presented view controller seems hacky, is there a better way to do this?
presentedViewController!.present(alertController, animated: true)
} else {
completion(true)
}
}
func draftSelected(_ draft: DraftsManager.Draft) {
self.currentDraft = draft
inReplyToID = draft.inReplyToID
updateInReplyTo()
statusTextView.text = draft.text
contentWarningEnabled = draft.contentWarning != nil
contentWarningTextField.text = draft.contentWarning

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15509"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -15,6 +15,8 @@
<outlet property="contentView" destination="pcX-rB-RxJ" id="o95-Qa-6N7"/>
<outlet property="contentWarningContainerView" destination="kU2-7l-MSy" id="Gnq-Jb-kCA"/>
<outlet property="contentWarningTextField" destination="T05-p6-vTz" id="Ivu-Ll-ByO"/>
<outlet property="inReplyToContainer" destination="2Dv-Q7-UEA" id="hfG-5j-G5R"/>
<outlet property="inReplyToLabel" destination="Y25-eP-tDE" id="9Ei-3s-dAx"/>
<outlet property="placeholderLabel" destination="EW3-YK-vPC" id="Rsw-Nv-TNz"/>
<outlet property="postProgressView" destination="Tq7-6P-hMT" id="amT-F1-JI0"/>
<outlet property="scrollView" destination="6Z0-Vy-hMX" id="ya0-2T-QaV"/>
@ -33,10 +35,10 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pcX-rB-RxJ">
<rect key="frame" x="0.0" y="0.0" width="375" height="342"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="371.5"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="bOB-hF-O9w">
<rect key="frame" x="0.0" y="0.0" width="375" height="342"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="371.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6V0-mH-Mhu">
<rect key="frame" x="0.0" y="0.0" width="375" height="66"/>
@ -63,11 +65,32 @@
<constraint firstItem="PMB-Wa-Ht0" firstAttribute="leading" secondItem="zZ3-Gv-4P5" secondAttribute="trailing" id="sVv-tH-7eB"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="2Dv-Q7-UEA">
<rect key="frame" x="0.0" y="66" width="375" height="33.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="In reply to Display Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Y25-eP-tDE">
<rect key="frame" x="4" y="8" width="367" height="21.5"/>
<constraints>
<constraint firstAttribute="height" constant="21.5" id="man-Xn-eVt"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="Y25-eP-tDE" secondAttribute="bottom" constant="4" id="1sZ-CX-GDU"/>
<constraint firstAttribute="trailing" secondItem="Y25-eP-tDE" secondAttribute="trailing" constant="4" id="I31-Rs-QwW"/>
<constraint firstItem="Y25-eP-tDE" firstAttribute="leading" secondItem="2Dv-Q7-UEA" secondAttribute="leading" constant="4" id="kdQ-zs-u7N"/>
<constraint firstItem="Y25-eP-tDE" firstAttribute="top" secondItem="2Dv-Q7-UEA" secondAttribute="top" constant="8" id="qdC-S5-CgV"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kU2-7l-MSy">
<rect key="frame" x="0.0" y="66" width="375" height="46"/>
<rect key="frame" x="0.0" y="99.5" width="375" height="42"/>
<subviews>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Write your warning here" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="T05-p6-vTz">
<rect key="frame" x="4" y="8" width="367" height="30"/>
<rect key="frame" x="4" y="4" width="367" height="30"/>
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="yzY-MF-Ukx"/>
@ -80,11 +103,11 @@
<constraint firstAttribute="trailing" secondItem="T05-p6-vTz" secondAttribute="trailing" constant="4" id="8tG-eW-TG4"/>
<constraint firstAttribute="bottom" secondItem="T05-p6-vTz" secondAttribute="bottom" constant="8" id="SUL-Hk-uvM"/>
<constraint firstItem="T05-p6-vTz" firstAttribute="leading" secondItem="kU2-7l-MSy" secondAttribute="leading" constant="4" id="WGG-B2-lPC"/>
<constraint firstItem="T05-p6-vTz" firstAttribute="top" secondItem="kU2-7l-MSy" secondAttribute="top" constant="8" id="lvW-S0-4k4"/>
<constraint firstItem="T05-p6-vTz" firstAttribute="top" secondItem="kU2-7l-MSy" secondAttribute="top" constant="4" id="dN2-Pf-qFQ"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="lhQ-ae-pe9">
<rect key="frame" x="0.0" y="112" width="375" height="150"/>
<rect key="frame" x="0.0" y="141.5" width="375" height="150"/>
<subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="9pn-0T-IHb">
<rect key="frame" x="4" y="0.0" width="367" height="150"/>
@ -112,7 +135,7 @@
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="P0F-3w-gI1">
<rect key="frame" x="0.0" y="262" width="375" height="80"/>
<rect key="frame" x="0.0" y="291.5" width="375" height="80"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="752-dD-eAO">
<rect key="frame" x="0.0" y="0.0" width="375" height="80"/>

View File

@ -10,6 +10,7 @@ import UIKit
protocol DraftsTableViewControllerDelegate {
func draftSelectionCanceled()
func shouldSelectDraft(_ draft: DraftsManager.Draft, completion: @escaping (Bool) -> Void)
func draftSelected(_ draft: DraftsManager.Draft)
func draftSelectionCompleted()
}
@ -61,9 +62,23 @@ class DraftsTableViewController: UITableViewController {
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
delegate?.draftSelected(draft(for: indexPath))
dismiss(animated: true) {
self.delegate?.draftSelectionCompleted()
let draft = self.draft(for: indexPath)
func select() {
delegate?.draftSelected(draft)
dismiss(animated: true) {
self.delegate?.draftSelectionCompleted()
}
}
if let delegate = delegate {
delegate.shouldSelectDraft(draft) { (shouldSelect) in
if shouldSelect {
select()
} else {
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
}
}
} else {
select()
}
}

View File

@ -113,10 +113,7 @@ class ConversationTableViewController: EnhancedTableViewController {
}
}
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
let statusID = statuses[indexPath.row].id
return statusID == mainStatusID ? nil : indexPath
}
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true

View File

@ -109,6 +109,7 @@ class NotificationsTableViewController: EnhancedTableViewController {
}
}
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == groups.count - 1 {

View File

@ -51,12 +51,10 @@ class InstanceSelectorTableViewController: UITableViewController {
case let .selected(instance):
let cell = tableView.dequeueReusableCell(withIdentifier: instanceCell, for: indexPath) as! InstanceTableViewCell
cell.updateUI(instance: instance)
cell.delegate = self
return cell
case let .recommended(instance):
let cell = tableView.dequeueReusableCell(withIdentifier: instanceCell, for: indexPath) as! InstanceTableViewCell
cell.updateUI(instance: instance)
cell.delegate = self
return cell
}
})
@ -135,6 +133,23 @@ class InstanceSelectorTableViewController: UITableViewController {
self.dataSource.apply(snapshot)
}
}
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let delegate = delegate, let item = dataSource.itemIdentifier(for: indexPath) else {
return
}
switch item {
case let .selected(instance):
delegate.didSelectInstance(url: URL(string: instance.uri)!)
case let .recommended(instance):
var components = URLComponents()
components.scheme = "https"
components.host = instance.domain
components.path = "/"
delegate.didSelectInstance(url: components.url!)
}
}
}
extension InstanceSelectorTableViewController {
@ -179,16 +194,3 @@ extension InstanceSelectorTableViewController: UISearchResultsUpdating {
urlCheckerSubject.send(currentQuery)
}
}
extension InstanceSelectorTableViewController: InstanceTableViewCellDelegate {
func didSelectInstance(_ instance: Instance) {
delegate?.didSelectInstance(url: URL(string: instance.uri)!)
}
func didSelectInstance(_ instance: InstanceSelector.Instance) {
var components = URLComponents()
components.scheme = "https"
components.host = instance.domain
components.path = "/"
delegate?.didSelectInstance(url: components.url!)
}
}

View File

@ -180,6 +180,8 @@ class ProfileTableViewController: EnhancedTableViewController {
}
}
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if timelineSegments.count > 0 && indexPath.section - 1 == timelineSegments.count && indexPath.row == timelineSegments[indexPath.section - 2].count - 1 {
guard let older = older else { return }
@ -245,17 +247,10 @@ extension ProfileTableViewController: ProfileHeaderTableViewCellDelegate {
MastodonCache.relationship(for: account.id) { [weak self] (relationship) in
guard let self = self else { return }
let customActivities: [UIActivity]
var customActivities: [UIActivity] = [OpenInSafariActivity()]
if let relationship = relationship {
let toggleFollowActivity = relationship.following ? UnfollowAccountActivity() : FollowAccountActivity()
customActivities = [
toggleFollowActivity,
OpenInSafariActivity()
]
} else {
customActivities = [
OpenInSafariActivity()
]
customActivities.insert(toggleFollowActivity, at: 0)
}
DispatchQueue.main.async {

View File

@ -156,8 +156,12 @@ extension SearchTableViewController {
}
class DataSource: UITableViewDiffableDataSource<Section, Item> {
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return Section.allCases[section].displayName
override func tableView(_ tableView: UITableView, titleForHeaderInSection sectionIndex: Int) -> String? {
let currentSnapshot = snapshot()
for section in Section.allCases where currentSnapshot.indexOfSection(section) == sectionIndex {
return section.displayName
}
return nil
}
}
}

View File

@ -35,6 +35,13 @@ class EnhancedTableViewController: UITableViewController {
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
prevScrollToTopOffset = nil
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? SelectableTableViewCell {
cell.didSelectCell()
}
}
}

View File

@ -14,27 +14,25 @@ protocol MenuPreviewProvider {
typealias PreviewProviders = (content: UIContextMenuContentPreviewProvider, actions: () -> [UIAction])
var navigationDelegate: TuskerNavigationDelegate? { get }
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders?
}
extension MenuPreviewProvider {
fileprivate func present(_ vc: UIViewController) {
UIApplication.shared.keyWindow!.rootViewController!.present(vc, animated: true)
}
func actionsForProfile(accountID: String) -> [UIAction] {
guard let account = MastodonCache.account(for: accountID) else { return [] }
return [
createAction(identifier: "openinsafari", title: "Open in Safari", systemImageName: "safari", handler: { (_) in
self.present(SFSafariViewController(url: account.url))
self.navigationDelegate?.selected(url: account.url)
}),
createAction(identifier: "sendmessage", title: "Send Message", systemImageName: "envelope", handler: { (_) in
self.present(UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct)))
self.navigationDelegate?.compose(mentioning: account.acct)
}),
createAction(identifier: "share", title: "Share...", systemImageName: "square.and.arrow.up", handler: { (_) in
self.present(UIActivityViewController(activityItems: [account.url], applicationActivities: nil))
self.navigationDelegate?.showMoreOptions(forAccount: accountID)
})
]
}
@ -42,10 +40,10 @@ extension MenuPreviewProvider {
func actionsForURL(_ url: URL) -> [UIAction] {
return [
createAction(identifier: "openinsafari", title: "Open in Safari", systemImageName: "safari", handler: { (_) in
self.present(SFSafariViewController(url: url))
self.navigationDelegate?.selected(url: url)
}),
createAction(identifier: "share", title: "Share...", systemImageName: "square.and.arrow.up", handler: { (_) in
self.present(UIActivityViewController(activityItems: [url], applicationActivities: nil))
self.navigationDelegate?.showMoreOptions(forURL: url)
})
]
}
@ -58,13 +56,13 @@ extension MenuPreviewProvider {
guard let status = MastodonCache.status(for: statusID) else { return [] }
return [
createAction(identifier: "reply", title: "Reply", systemImageName: "arrowshape.turn.up.left", handler: { (_) in
self.present(UINavigationController(rootViewController: ComposeViewController(inReplyTo: statusID)))
self.navigationDelegate?.reply(to: statusID)
}),
createAction(identifier: "openinsafari", title: "Open in Safari", systemImageName: "safari", handler: { (_) in
self.present(SFSafariViewController(url: status.url!))
self.navigationDelegate?.selected(url: status.url!)
}),
createAction(identifier: "share", title: "Share...", systemImageName: "square.and.arrow.up", handler: { (_) in
self.present(UIActivityViewController(activityItems: [status.url!], applicationActivities: nil))
self.navigationDelegate?.showMoreOptions(forStatus: statusID)
})
]
}

View File

@ -28,6 +28,8 @@ protocol TuskerNavigationDelegate {
func compose()
func compose(mentioning: String?)
func reply(to statusID: String)
func largeImage(_ image: UIImage, description: String?, sourceView: UIImageView) -> LargeImageViewController
@ -44,6 +46,8 @@ protocol TuskerNavigationDelegate {
func showMoreOptions(forStatus statusID: String)
func showMoreOptions(forAccount accountID: String)
func showMoreOptions(forURL url: URL)
func showFollowedByList(accountIDs: [String])
@ -112,8 +116,13 @@ extension TuskerNavigationDelegate where Self: UIViewController {
show(ConversationTableViewController(for: statusID, state: state), sender: self)
}
// protocols can't have parameter defaults, so this stub is necessary to fulfill the protocol req
func compose() {
let compose = ComposeViewController()
compose(mentioning: nil)
}
func compose(mentioning: String? = nil) {
let compose = ComposeViewController( mentioningAcct: mentioning)
let vc = UINavigationController(rootViewController: compose)
vc.presentationController?.delegate = compose
present(vc, animated: true)
@ -185,7 +194,20 @@ extension TuskerNavigationDelegate where Self: UIViewController {
private func moreOptions(forStatus statusID: String) -> UIViewController {
guard let status = MastodonCache.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
guard let url = status.url else { fatalError("Missing url for status \(statusID)") }
return moreOptions(forURL: url)
var customActivites: [UIActivity] = [OpenInSafariActivity()]
if let bookmarked = status.bookmarked {
customActivites.insert(bookmarked ? UnbookmarkStatusActivity() : BookmarkStatusActivity(), at: 0)
}
let activityController = UIActivityViewController(activityItems: [url, status], applicationActivities: customActivites)
activityController.completionWithItemsHandler = OpenInSafariActivity.completionHandler(viewController: self, url: url)
return activityController
}
private func moreOptions(forAccount accountID: String) -> UIViewController {
guard let account = MastodonCache.account(for: accountID) else { fatalError("Missing cached account \(accountID)") }
return moreOptions(forURL: account.url)
}
func showMoreOptions(forStatus statusID: String) {
@ -196,6 +218,10 @@ extension TuskerNavigationDelegate where Self: UIViewController {
present(moreOptions(forURL: url), animated: true)
}
func showMoreOptions(forAccount accountID: String) {
present(moreOptions(forAccount: accountID), animated: true)
}
func showFollowedByList(accountIDs: [String]) {
let vc = AccountListTableViewController(accountIDs: accountIDs)
vc.title = NSLocalizedString("Followed By", comment: "followed by accounts list title")

View File

@ -56,20 +56,18 @@ class AccountTableViewCell: UITableViewCell {
updateUIForPrefrences()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
if selected {
delegate?.selected(account: accountID)
}
extension AccountTableViewCell: SelectableTableViewCell {
func didSelectCell() {
delegate?.selected(account: accountID)
}
}
extension AccountTableViewCell: MenuPreviewProvider {
var navigationDelegate: TuskerNavigationDelegate? { return delegate }
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
return (content: { ProfileTableViewController(accountID: self.accountID) }, actions: { self.actionsForProfile(accountID: self.accountID) })
}
}

View File

@ -23,12 +23,10 @@ class HashtagTableViewCell: UITableViewCell {
hashtagLabel.text = "#\(hashtag.name)"
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
delegate?.selected(tag: hashtag)
}
}
}
extension HashtagTableViewCell: SelectableTableViewCell {
func didSelectCell() {
delegate?.selected(tag: hashtag)
}
}

View File

@ -9,15 +9,8 @@
import UIKit
import Pachyderm
protocol InstanceTableViewCellDelegate {
func didSelectInstance(_ instance: Instance)
func didSelectInstance(_ instance: InstanceSelector.Instance)
}
class InstanceTableViewCell: UITableViewCell {
var delegate: InstanceTableViewCellDelegate?
@IBOutlet weak var thumbnailImageView: UIImageView!
@IBOutlet weak var domainLabel: UILabel!
@IBOutlet weak var adultLabel: UILabel!
@ -74,16 +67,4 @@ class InstanceTableViewCell: UITableViewCell {
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected, let delegate = delegate {
if let instance = instance {
delegate.didSelectInstance(instance)
} else if let instance = selectorInstance {
delegate.didSelectInstance(instance)
}
}
}
}

View File

@ -0,0 +1,13 @@
//
// NavigableTableViewCell.swift
// Tusker
//
// Created by Shadowfacts on 12/14/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import Foundation
protocol NavigableTableViewCell {
var navigationDelegate: TuskerNavigationDelegate? { get }
}

View File

@ -149,30 +149,30 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
updateTimestampWorkItem?.cancel()
updateTimestampWorkItem = nil
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected, let delegate = delegate {
let notifications = group.notificationIDs.compactMap(MastodonCache.notification(for:))
let accountIDs = notifications.map { $0.account.id }
let action: StatusActionAccountListTableViewController.ActionType
switch notifications.first!.kind {
case .favourite:
action = .favorite
case .reblog:
action = .reblog
default:
fatalError()
}
let vc = delegate.statusActionAccountList(action: action, statusID: statusID, statusState: .unknown, accountIDs: accountIDs)
delegate.show(vc)
}
}
}
extension ActionNotificationGroupTableViewCell: SelectableTableViewCell {
func didSelectCell() {
guard let delegate = delegate else { return }
let notifications = group.notificationIDs.compactMap(MastodonCache.notification(for:))
let accountIDs = notifications.map { $0.account.id }
let action: StatusActionAccountListTableViewController.ActionType
switch notifications.first!.kind {
case .favourite:
action = .favorite
case .reblog:
action = .reblog
default:
fatalError()
}
let vc = delegate.statusActionAccountList(action: action, statusID: statusID, statusState: .unknown, accountIDs: accountIDs)
delegate.show(vc)
}
}
extension ActionNotificationGroupTableViewCell: MenuPreviewProvider {
var navigationDelegate: TuskerNavigationDelegate? { return delegate }
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
return (content: {
@ -192,5 +192,4 @@ extension ActionNotificationGroupTableViewCell: MenuPreviewProvider {
return []
})
}
}

View File

@ -112,26 +112,25 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
updateTimestampWorkItem?.cancel()
updateTimestampWorkItem = nil
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
let people = group.notificationIDs.compactMap(MastodonCache.notification(for:)).map { $0.account.id }
switch people.count {
case 0:
return
case 1:
delegate?.selected(account: people.first!)
default:
delegate?.showFollowedByList(accountIDs: people)
}
}
extension FollowNotificationGroupTableViewCell: SelectableTableViewCell {
func didSelectCell() {
let people = group.notificationIDs.compactMap(MastodonCache.notification(for:)).map { $0.account.id }
switch people.count {
case 0:
return
case 1:
delegate?.selected(account: people.first!)
default:
delegate?.showFollowedByList(accountIDs: people)
}
}
}
extension FollowNotificationGroupTableViewCell: MenuPreviewProvider {
var navigationDelegate: TuskerNavigationDelegate? { return delegate }
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
return (content: {
@ -145,5 +144,4 @@ extension FollowNotificationGroupTableViewCell: MenuPreviewProvider {
return []
})
}
}

View File

@ -151,6 +151,7 @@ class ProfileHeaderTableViewCell: UITableViewCell {
}
extension ProfileHeaderTableViewCell: MenuPreviewProvider {
var navigationDelegate: TuskerNavigationDelegate? { return delegate }
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
let noteLabelPoint = noteLabel.convert(location, from: self)
if noteLabel.bounds.contains(noteLabelPoint),

View File

@ -0,0 +1,13 @@
//
// SelectableTableViewCell.swift
// Tusker
//
// Created by Shadowfacts on 12/14/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import UIKit
protocol SelectableTableViewCell {
func didSelectCell()
}

View File

@ -15,13 +15,12 @@ protocol StatusTableViewCellDelegate: TuskerNavigationDelegate {
}
class BaseStatusTableViewCell: UITableViewCell {
var delegate: StatusTableViewCellDelegate? {
didSet {
contentLabel.navigationDelegate = delegate
}
}
@IBOutlet weak var avatarImageView: UIImageView!
@IBOutlet weak var displayNameLabel: UILabel!
@IBOutlet weak var usernameLabel: UILabel!
@ -311,6 +310,8 @@ extension BaseStatusTableViewCell: AttachmentViewDelegate {
}
extension BaseStatusTableViewCell: MenuPreviewProvider {
var navigationDelegate: TuskerNavigationDelegate? { return delegate }
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
if avatarImageView.frame.contains(location) {
return (content: { ProfileTableViewController(accountID: self.accountID)}, actions: { self.actionsForProfile(accountID: self.accountID) })

View File

@ -119,14 +119,6 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
showPinned = false
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
delegate?.selected(status: statusID, state: statusState.copy())
}
}
@objc func reblogLabelPressed() {
guard let rebloggerID = rebloggerID else { return }
delegate?.selected(account: rebloggerID)
@ -141,6 +133,12 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
}
extension TimelineStatusTableViewCell: SelectableTableViewCell {
func didSelectCell() {
delegate?.selected(status: statusID, state: statusState.copy())
}
}
extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
func leadingSwipeActionsConfiguration() -> UISwipeActionsConfiguration? {