From 0c78af7d4f2ca7052fa0320ee57ef32065e96da3 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 14 Dec 2019 11:30:35 -0500 Subject: [PATCH] Store in reply to status in drafts --- Tusker/DraftsManager.swift | 12 +- .../Compose/ComposeViewController.swift | 123 +++++++++++------- .../Screens/Compose/ComposeViewController.xib | 41 ++++-- .../Drafts/DraftsTableViewController.swift | 21 ++- 4 files changed, 135 insertions(+), 62 deletions(-) diff --git a/Tusker/DraftsManager.swift b/Tusker/DraftsManager.swift index 95878030..6cfccee3 100644 --- a/Tusker/DraftsManager.swift +++ b/Tusker/DraftsManager.swift @@ -39,8 +39,8 @@ class DraftsManager: Codable { return drafts.sorted(by: { $0.lastModified > $1.lastModified }) } - func create(text: String, contentWarning: String?, attachments: [DraftAttachment]) -> Draft { - let draft = 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 } @@ -57,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]) { diff --git a/Tusker/Screens/Compose/ComposeViewController.swift b/Tusker/Screens/Compose/ComposeViewController.swift index b7694407..51751f09 100644 --- a/Tusker/Screens/Compose/ComposeViewController.swift +++ b/Tusker/Screens/Compose/ComposeViewController.swift @@ -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,7 +350,7 @@ 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, attachments: attachments) + self.currentDraft = DraftsManager.shared.create(text: self.statusTextView.text, contentWarning: cw, inReplyToID: inReplyToID, attachments: attachments) } DraftsManager.save() } @@ -598,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 diff --git a/Tusker/Screens/Compose/ComposeViewController.xib b/Tusker/Screens/Compose/ComposeViewController.xib index 7142258a..3d052e77 100644 --- a/Tusker/Screens/Compose/ComposeViewController.xib +++ b/Tusker/Screens/Compose/ComposeViewController.xib @@ -1,8 +1,8 @@ - + - + @@ -15,6 +15,8 @@ + + @@ -33,10 +35,10 @@ - + - + @@ -63,11 +65,32 @@ + + + + + + + + + + + + + - + - + @@ -80,11 +103,11 @@ - + - + @@ -112,7 +135,7 @@ - + diff --git a/Tusker/Screens/Compose/Drafts/DraftsTableViewController.swift b/Tusker/Screens/Compose/Drafts/DraftsTableViewController.swift index 90a34370..3f71e3f2 100644 --- a/Tusker/Screens/Compose/Drafts/DraftsTableViewController.swift +++ b/Tusker/Screens/Compose/Drafts/DraftsTableViewController.swift @@ -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() } }