Compare commits

..

No commits in common. "382decd7da2f508fc6a188b6324592522529772f" and "06442b5629a5d673f57c8f729effc8666c958962" have entirely different histories.

29 changed files with 192 additions and 482 deletions

View File

@ -35,7 +35,6 @@ 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")
@ -85,14 +84,6 @@ 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")
}
@ -127,7 +118,6 @@ public class Status: Decodable {
case application
case language
case pinned
case bookmarked
}
}

View File

@ -76,11 +76,6 @@
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 */; };
@ -344,11 +339,6 @@
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>"; };
@ -707,16 +697,6 @@
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 = (
@ -1061,9 +1041,8 @@
isa = PBXGroup;
children = (
D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */,
D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */,
D6AEBB4623216B0C00E5038B /* Account Activities */,
D627943323A5523800D38C68 /* Status Activities */,
D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */,
);
path = Activities;
sourceTree = "<group>";
@ -1095,8 +1074,6 @@
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 */,
@ -1600,7 +1577,6 @@
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 */,
@ -1627,7 +1603,6 @@
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 */,
@ -1635,17 +1610,14 @@
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

@ -1,41 +0,0 @@
//
// 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

@ -1,34 +0,0 @@
//
// 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

@ -1,41 +0,0 @@
//
// 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,7 +18,5 @@ 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,12 +16,10 @@ class DraftsManager: Codable {
private static var archiveURL = DraftsManager.documentsDirectory.appendingPathComponent("drafts").appendingPathExtension("plist")
static func save() {
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 {
let decoder = PropertyListDecoder()
@ -39,10 +37,8 @@ class DraftsManager: Codable {
return drafts.sorted(by: { $0.lastModified > $1.lastModified })
}
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 create(text: String, contentWarning: String?, attachments: [DraftAttachment]) {
drafts.append(Draft(text: text, contentWarning: contentWarning, lastModified: Date(), attachments: attachments))
}
func remove(_ draft: Draft) {
@ -57,17 +53,15 @@ extension DraftsManager {
let id: UUID
private(set) var text: String
private(set) var contentWarning: String?
private(set) var attachments: [DraftAttachment]
private(set) var inReplyToID: String?
private(set) var lastModified: Date
private(set) var attachments: [DraftAttachment]
init(text: String, contentWarning: String?, inReplyToID: String?, attachments: [DraftAttachment], lastModified: Date = Date()) {
init(text: String, contentWarning: String?, lastModified: Date, attachments: [DraftAttachment]) {
self.id = UUID()
self.text = text
self.contentWarning = contentWarning
self.inReplyToID = inReplyToID
self.attachments = attachments
self.lastModified = lastModified
self.attachments = attachments
}
func update(text: String, contentWarning: String?, attachments: [DraftAttachment]) {

View File

@ -12,10 +12,10 @@ import Pachyderm
class MastodonCache {
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")
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]()
static let statusSubject = PassthroughSubject<Status, Never>()
static let accountSubject = PassthroughSubject<Account, Never>()
@ -134,29 +134,3 @@ 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,8 +57,6 @@ 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!
@ -129,45 +127,7 @@ class ComposeViewController: UIViewController {
}
}
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)
updateCharactersRemaining()
updatePlaceholder()
NotificationCenter.default.addObserver(self, selector: #selector(contentWarningTextFieldDidChange), name: UITextField.textDidChangeNotification, object: contentWarningTextField)
}
func updateInReplyTo() {
if let replyView = replyView {
replyView.removeFromSuperview()
}
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) {
if let inReplyToID = inReplyToID, let inReplyTo = MastodonCache.status(for: inReplyToID) {
visibility = inReplyTo.visibility
if Preferences.shared.contentWarningCopyMode == .doNotCopy {
contentWarningEnabled = false
@ -186,14 +146,42 @@ class ComposeViewController: UIViewController {
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)"
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)
}
// we have to set the font here, because the monospaced digit font is not available in IB
charactersRemainingLabel.font = .monospacedDigitSystemFont(ofSize: 17, weight: .regular)
updateCharactersRemaining()
updatePlaceholder()
NotificationCenter.default.addObserver(self, selector: #selector(contentWarningTextFieldDidChange), name: UITextField.textDidChangeNotification, object: contentWarningTextField)
if inReplyToID == nil {
visibility = Preferences.shared.defaultPostVisibility
contentWarningEnabled = false
}
}
override func viewWillAppear(_ animated: Bool) {
@ -350,9 +338,8 @@ class ComposeViewController: UIViewController {
if let currentDraft = self.currentDraft {
currentDraft.update(text: self.statusTextView.text, contentWarning: cw, attachments: attachments)
} else {
self.currentDraft = DraftsManager.shared.create(text: self.statusTextView.text, contentWarning: cw, inReplyToID: inReplyToID, attachments: attachments)
DraftsManager.shared.create(text: self.statusTextView.text, contentWarning: cw, attachments: attachments)
}
DraftsManager.save()
}
@objc func close() {
@ -610,30 +597,9 @@ 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="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<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">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15509"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -15,8 +15,6 @@
<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"/>
@ -35,10 +33,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="371.5"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="342"/>
<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="371.5"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="342"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6V0-mH-Mhu">
<rect key="frame" x="0.0" y="0.0" width="375" height="66"/>
@ -65,32 +63,11 @@
<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="99.5" width="375" height="42"/>
<rect key="frame" x="0.0" y="66" width="375" height="46"/>
<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="4" width="367" height="30"/>
<rect key="frame" x="4" y="8" 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"/>
@ -103,11 +80,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="4" id="dN2-Pf-qFQ"/>
<constraint firstItem="T05-p6-vTz" firstAttribute="top" secondItem="kU2-7l-MSy" secondAttribute="top" constant="8" id="lvW-S0-4k4"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="lhQ-ae-pe9">
<rect key="frame" x="0.0" y="141.5" width="375" height="150"/>
<rect key="frame" x="0.0" y="112" 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"/>
@ -135,7 +112,7 @@
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="P0F-3w-gI1">
<rect key="frame" x="0.0" y="291.5" width="375" height="80"/>
<rect key="frame" x="0.0" y="262" 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,7 +10,6 @@ import UIKit
protocol DraftsTableViewControllerDelegate {
func draftSelectionCanceled()
func shouldSelectDraft(_ draft: DraftsManager.Draft, completion: @escaping (Bool) -> Void)
func draftSelected(_ draft: DraftsManager.Draft)
func draftSelectionCompleted()
}
@ -62,25 +61,11 @@ class DraftsTableViewController: UITableViewController {
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let draft = self.draft(for: indexPath)
func select() {
delegate?.draftSelected(draft)
delegate?.draftSelected(draft(for: indexPath))
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()
}
}
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete

View File

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

View File

@ -109,7 +109,6 @@ 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,10 +51,12 @@ 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
}
})
@ -133,23 +135,6 @@ 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 {
@ -194,3 +179,16 @@ 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,8 +180,6 @@ 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 }
@ -247,10 +245,17 @@ extension ProfileTableViewController: ProfileHeaderTableViewCellDelegate {
MastodonCache.relationship(for: account.id) { [weak self] (relationship) in
guard let self = self else { return }
var customActivities: [UIActivity] = [OpenInSafariActivity()]
let customActivities: [UIActivity]
if let relationship = relationship {
let toggleFollowActivity = relationship.following ? UnfollowAccountActivity() : FollowAccountActivity()
customActivities.insert(toggleFollowActivity, at: 0)
customActivities = [
toggleFollowActivity,
OpenInSafariActivity()
]
} else {
customActivities = [
OpenInSafariActivity()
]
}
DispatchQueue.main.async {

View File

@ -156,12 +156,8 @@ extension SearchTableViewController {
}
class DataSource: UITableViewDiffableDataSource<Section, Item> {
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
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return Section.allCases[section].displayName
}
}
}

View File

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

View File

@ -14,25 +14,27 @@ 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.navigationDelegate?.selected(url: account.url)
self.present(SFSafariViewController(url: account.url))
}),
createAction(identifier: "sendmessage", title: "Send Message", systemImageName: "envelope", handler: { (_) in
self.navigationDelegate?.compose(mentioning: account.acct)
self.present(UINavigationController(rootViewController: ComposeViewController(mentioningAcct: account.acct)))
}),
createAction(identifier: "share", title: "Share...", systemImageName: "square.and.arrow.up", handler: { (_) in
self.navigationDelegate?.showMoreOptions(forAccount: accountID)
self.present(UIActivityViewController(activityItems: [account.url], applicationActivities: nil))
})
]
}
@ -40,10 +42,10 @@ extension MenuPreviewProvider {
func actionsForURL(_ url: URL) -> [UIAction] {
return [
createAction(identifier: "openinsafari", title: "Open in Safari", systemImageName: "safari", handler: { (_) in
self.navigationDelegate?.selected(url: url)
self.present(SFSafariViewController(url: url))
}),
createAction(identifier: "share", title: "Share...", systemImageName: "square.and.arrow.up", handler: { (_) in
self.navigationDelegate?.showMoreOptions(forURL: url)
self.present(UIActivityViewController(activityItems: [url], applicationActivities: nil))
})
]
}
@ -56,13 +58,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.navigationDelegate?.reply(to: statusID)
self.present(UINavigationController(rootViewController: ComposeViewController(inReplyTo: statusID)))
}),
createAction(identifier: "openinsafari", title: "Open in Safari", systemImageName: "safari", handler: { (_) in
self.navigationDelegate?.selected(url: status.url!)
self.present(SFSafariViewController(url: status.url!))
}),
createAction(identifier: "share", title: "Share...", systemImageName: "square.and.arrow.up", handler: { (_) in
self.navigationDelegate?.showMoreOptions(forStatus: statusID)
self.present(UIActivityViewController(activityItems: [status.url!], applicationActivities: nil))
})
]
}

View File

@ -28,8 +28,6 @@ protocol TuskerNavigationDelegate {
func compose()
func compose(mentioning: String?)
func reply(to statusID: String)
func largeImage(_ image: UIImage, description: String?, sourceView: UIImageView) -> LargeImageViewController
@ -46,8 +44,6 @@ protocol TuskerNavigationDelegate {
func showMoreOptions(forStatus statusID: String)
func showMoreOptions(forAccount accountID: String)
func showMoreOptions(forURL url: URL)
func showFollowedByList(accountIDs: [String])
@ -116,13 +112,8 @@ 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() {
compose(mentioning: nil)
}
func compose(mentioning: String? = nil) {
let compose = ComposeViewController( mentioningAcct: mentioning)
let compose = ComposeViewController()
let vc = UINavigationController(rootViewController: compose)
vc.presentationController?.delegate = compose
present(vc, animated: true)
@ -194,20 +185,7 @@ 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)") }
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)
return moreOptions(forURL: url)
}
func showMoreOptions(forStatus statusID: String) {
@ -218,10 +196,6 @@ 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,18 +56,20 @@ class AccountTableViewCell: UITableViewCell {
updateUIForPrefrences()
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
extension AccountTableViewCell: SelectableTableViewCell {
func didSelectCell() {
if selected {
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,10 +23,12 @@ class HashtagTableViewCell: UITableViewCell {
hashtagLabel.text = "#\(hashtag.name)"
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
extension HashtagTableViewCell: SelectableTableViewCell {
func didSelectCell() {
if selected {
delegate?.selected(tag: hashtag)
}
}
}

View File

@ -9,8 +9,15 @@
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!
@ -67,4 +74,16 @@ 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

@ -1,13 +0,0 @@
//
// 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

@ -150,11 +150,10 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
updateTimestampWorkItem = nil
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
extension ActionNotificationGroupTableViewCell: SelectableTableViewCell {
func didSelectCell() {
guard let delegate = delegate else { return }
if selected, let delegate = delegate {
let notifications = group.notificationIDs.compactMap(MastodonCache.notification(for:))
let accountIDs = notifications.map { $0.account.id }
let action: StatusActionAccountListTableViewController.ActionType
@ -169,10 +168,11 @@ extension ActionNotificationGroupTableViewCell: SelectableTableViewCell {
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,4 +192,5 @@ extension ActionNotificationGroupTableViewCell: MenuPreviewProvider {
return []
})
}
}

View File

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

View File

@ -151,7 +151,6 @@ 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

@ -1,13 +0,0 @@
//
// 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,6 +15,7 @@ protocol StatusTableViewCellDelegate: TuskerNavigationDelegate {
}
class BaseStatusTableViewCell: UITableViewCell {
var delegate: StatusTableViewCellDelegate? {
didSet {
contentLabel.navigationDelegate = delegate
@ -310,8 +311,6 @@ 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,6 +119,14 @@ 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)
@ -133,12 +141,6 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
}
extension TimelineStatusTableViewCell: SelectableTableViewCell {
func didSelectCell() {
delegate?.selected(status: statusID, state: statusState.copy())
}
}
extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
func leadingSwipeActionsConfiguration() -> UISwipeActionsConfiguration? {