forked from shadowfacts/Tusker
601 lines
26 KiB
Swift
601 lines
26 KiB
Swift
//
|
|
// ComposeViewController.swift
|
|
// Tusker
|
|
//
|
|
// Created by Shadowfacts on 8/28/18.
|
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import Pachyderm
|
|
import Intents
|
|
|
|
class ComposeViewController: UIViewController {
|
|
|
|
weak var mastodonController: MastodonController!
|
|
|
|
var inReplyToID: String?
|
|
var accountsToMention = [String]()
|
|
var initialText: String?
|
|
var contentWarningEnabled = false {
|
|
didSet {
|
|
contentWarningStateChanged()
|
|
}
|
|
}
|
|
var visibility: Status.Visibility! {
|
|
didSet {
|
|
visibilityChanged()
|
|
}
|
|
}
|
|
|
|
var hasChanges = false
|
|
var currentDraft: DraftsManager.Draft?
|
|
// Weak so that if a new session is initiated (i.e. XCBManager.currentSession is changed) while the current one is in progress, this one will be released
|
|
weak var xcbSession: XCBSession?
|
|
var postedStatus: Status?
|
|
|
|
var compositionState: CompositionState = .valid {
|
|
didSet {
|
|
postBarButtonItem.isEnabled = compositionState.isValid
|
|
}
|
|
}
|
|
|
|
weak var postBarButtonItem: UIBarButtonItem!
|
|
var visibilityBarButtonItem: UIBarButtonItem!
|
|
var contentWarningBarButtonItem: UIBarButtonItem!
|
|
|
|
@IBOutlet weak var scrollView: UIScrollView!
|
|
@IBOutlet weak var contentView: UIView!
|
|
@IBOutlet weak var stackView: UIStackView!
|
|
|
|
var replyView: ComposeStatusReplyView?
|
|
var replyAvatarImageViewTopConstraint: NSLayoutConstraint?
|
|
|
|
@IBOutlet weak var selfDetailView: LargeAccountDetailView!
|
|
|
|
@IBOutlet weak var charactersRemainingLabel: UILabel!
|
|
@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!
|
|
|
|
@IBOutlet weak var composeAttachmentsContainerView: UIView!
|
|
|
|
@IBOutlet weak var postProgressView: SteppedProgressView!
|
|
|
|
var composeAttachmentsViewController: ComposeAttachmentsViewController!
|
|
|
|
init(inReplyTo inReplyToID: String? = nil, mentioningAcct: String? = nil, text: String? = nil, mastodonController: MastodonController) {
|
|
self.mastodonController = mastodonController
|
|
|
|
self.inReplyToID = inReplyToID
|
|
if let inReplyToID = inReplyToID, let inReplyTo = mastodonController.cache.status(for: inReplyToID) {
|
|
accountsToMention = [inReplyTo.account.acct] + inReplyTo.mentions.map { $0.acct }
|
|
} else {
|
|
accountsToMention = []
|
|
}
|
|
if let mentioningAcct = mentioningAcct {
|
|
accountsToMention.append(mentioningAcct)
|
|
}
|
|
if let ownAccount = mastodonController.account {
|
|
accountsToMention.removeAll(where: { acct in ownAccount.acct == acct })
|
|
}
|
|
accountsToMention = accountsToMention.uniques()
|
|
|
|
super.init(nibName: "ComposeViewController", bundle: nil)
|
|
|
|
title = "Compose"
|
|
tabBarItem.image = UIImage(systemName: "pencil")
|
|
|
|
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(showSaveAndClosePrompt))
|
|
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Post", style: .done, target: self, action: #selector(postButtonPressed))
|
|
postBarButtonItem = navigationItem.rightBarButtonItem
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
scrollView.delegate = self
|
|
|
|
statusTextView.delegate = self
|
|
statusTextView.becomeFirstResponder()
|
|
|
|
let toolbar = UIToolbar()
|
|
contentWarningBarButtonItem = UIBarButtonItem(title: "CW", style: .plain, target: self, action: #selector(contentWarningButtonPressed))
|
|
contentWarningBarButtonItem.accessibilityLabel = NSLocalizedString("Add Content Warning", comment: "add CW accessibility label")
|
|
visibilityBarButtonItem = UIBarButtonItem(image: UIImage(systemName: Preferences.shared.defaultPostVisibility.imageName), style: .plain, target: self, action: #selector(visibilityButtonPressed))
|
|
visibilityBarButtonItem.accessibilityLabel = String(format: NSLocalizedString("Visibility: %@", comment: "compose visiblity accessibility label"), Preferences.shared.defaultPostVisibility.displayName)
|
|
toolbar.items = [
|
|
contentWarningBarButtonItem,
|
|
visibilityBarButtonItem,
|
|
UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
|
|
] + createFormattingButtons() + [
|
|
UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil),
|
|
UIBarButtonItem(title: "Drafts", style: .plain, target: self, action: #selector(draftsButtonPressed))
|
|
]
|
|
toolbar.translatesAutoresizingMaskIntoConstraints = false
|
|
statusTextView.inputAccessoryView = toolbar
|
|
contentWarningTextField.inputAccessoryView = toolbar
|
|
|
|
statusTextView.text = accountsToMention.map({ acct in "@\(acct) " }).joined()
|
|
initialText = statusTextView.text
|
|
|
|
mastodonController.getOwnAccount { (account) in
|
|
DispatchQueue.main.async {
|
|
self.selfDetailView.update(account: account)
|
|
}
|
|
}
|
|
|
|
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()
|
|
|
|
composeAttachmentsViewController = ComposeAttachmentsViewController(attachments: currentDraft?.attachments ?? [], mastodonController: mastodonController)
|
|
composeRequiresAttachmentDescriptionsDidChange()
|
|
composeAttachmentsViewController.delegate = self
|
|
composeAttachmentsViewController.tableView.isScrollEnabled = false
|
|
composeAttachmentsViewController.tableView.translatesAutoresizingMaskIntoConstraints = false
|
|
embedChild(composeAttachmentsViewController, in: composeAttachmentsContainerView)
|
|
|
|
pasteConfiguration = composeAttachmentsViewController.pasteConfiguration
|
|
|
|
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 = mastodonController.cache.status(for: inReplyToID) {
|
|
updateInReplyTo(inReplyTo: status)
|
|
} else {
|
|
let loadingVC = LoadingViewController()
|
|
embedChild(loadingVC)
|
|
|
|
mastodonController.cache.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.mastodonController = mastodonController
|
|
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.displayOrUserName)"
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
override func viewWillDisappear(_ animated: Bool) {
|
|
super.viewWillDisappear(animated)
|
|
|
|
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
|
|
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
|
|
}
|
|
|
|
func createFormattingButtons() -> [UIBarButtonItem] {
|
|
guard Preferences.shared.statusContentType != .plain else {
|
|
return []
|
|
}
|
|
|
|
return StatusFormat.allCases.map { (format) in
|
|
let item: UIBarButtonItem
|
|
if let image = format.image {
|
|
item = UIBarButtonItem(image: image, style: .plain, target: self, action: #selector(formatButtonPressed(_:)))
|
|
} else if let (str, attributes) = format.title {
|
|
item = UIBarButtonItem(title: str, style: .plain, target: self, action: #selector(formatButtonPressed(_:)))
|
|
item.setTitleTextAttributes(attributes, for: .normal)
|
|
item.setTitleTextAttributes(attributes, for: .highlighted)
|
|
} else {
|
|
fatalError("StatusFormat must have either an image or a title")
|
|
}
|
|
item.tag = StatusFormat.allCases.firstIndex(of: format)!
|
|
item.accessibilityLabel = format.accessibilityLabel
|
|
return item
|
|
}
|
|
}
|
|
|
|
@objc func adjustForKeyboard(notification: NSNotification) {
|
|
let userInfo = notification.userInfo!
|
|
|
|
let keyboardScreenEndFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
|
|
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
|
|
|
|
if notification.name == UIResponder.keyboardWillHideNotification {
|
|
scrollView.contentInset = .zero
|
|
} else {
|
|
// let accessoryFrame = view.convert(statusTextView.inputAccessoryView!.frame, from: view.window)
|
|
let offset = keyboardViewEndFrame.height// + accessoryFrame.height
|
|
// TODO: radar for incorrect keyboard end frame height (either converted or screen)
|
|
// the value returned is somewhere between the height of the keyboard and the height of the keyboard + accessory
|
|
// actually maybe not??
|
|
scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: offset, right: 0)
|
|
}
|
|
scrollView.scrollIndicatorInsets = scrollView.contentInset
|
|
}
|
|
|
|
func updateCharactersRemaining() {
|
|
let count = CharacterCounter.count(text: statusTextView.text)
|
|
let cwCount = contentWarningEnabled ? (contentWarningTextField.text?.count ?? 0) : 0
|
|
let remaining = (mastodonController.instance.maxStatusCharacters ?? 500) - count - cwCount
|
|
if remaining < 0 {
|
|
charactersRemainingLabel.textColor = .red
|
|
compositionState.formUnion(.tooManyCharacters)
|
|
} else {
|
|
charactersRemainingLabel.textColor = .darkGray
|
|
compositionState.subtract(.tooManyCharacters)
|
|
}
|
|
charactersRemainingLabel.text = String(remaining)
|
|
charactersRemainingLabel.accessibilityLabel = String(format: NSLocalizedString("%d characters remaining", comment: "compose characters remaining accessibility label"), remaining)
|
|
}
|
|
|
|
func updateHasChanges() {
|
|
if let currentDraft = currentDraft {
|
|
let cw = contentWarningEnabled ? contentWarningTextField.text : nil
|
|
hasChanges = statusTextView.text != currentDraft.text || cw != currentDraft.contentWarning
|
|
} else {
|
|
hasChanges = !statusTextView.text.isEmpty || (contentWarningEnabled && !(contentWarningTextField.text?.isEmpty ?? true))
|
|
}
|
|
}
|
|
|
|
func updatePlaceholder() {
|
|
placeholderLabel.isHidden = !statusTextView.text.isEmpty
|
|
}
|
|
|
|
func contentWarningStateChanged() {
|
|
contentWarningContainerView.isHidden = !contentWarningEnabled
|
|
if contentWarningEnabled {
|
|
contentWarningBarButtonItem.accessibilityLabel = NSLocalizedString("Remove Content Warning", comment: "remove CW accessibility label")
|
|
} else {
|
|
contentWarningBarButtonItem.accessibilityLabel = NSLocalizedString("Add Content Warning", comment: "add CW accessibility label")
|
|
}
|
|
}
|
|
|
|
func visibilityChanged() {
|
|
visibilityBarButtonItem.image = UIImage(systemName: visibility.imageName)
|
|
visibilityBarButtonItem.accessibilityLabel = String(format: NSLocalizedString("Visibility: %@", comment: "compose visiblity accessibility label"), Preferences.shared.defaultPostVisibility.displayName)
|
|
}
|
|
|
|
func saveDraft() {
|
|
let attachments = composeAttachmentsViewController.attachments
|
|
let statusText = statusTextView.text.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let cw = contentWarningEnabled ? contentWarningTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) : nil
|
|
let account = mastodonController.accountInfo!
|
|
if attachments.count == 0, statusText.isEmpty, cw?.isEmpty ?? true {
|
|
if let currentDraft = self.currentDraft {
|
|
DraftsManager.shared.remove(currentDraft)
|
|
} else {
|
|
return
|
|
}
|
|
} else {
|
|
if let currentDraft = self.currentDraft {
|
|
currentDraft.update(accountID: account.id, text: statusText, contentWarning: cw, attachments: attachments)
|
|
} else {
|
|
self.currentDraft = DraftsManager.shared.create(accountID: account.id, text: statusText, contentWarning: cw, inReplyToID: inReplyToID, attachments: attachments)
|
|
}
|
|
}
|
|
DraftsManager.save()
|
|
}
|
|
|
|
@objc func close() {
|
|
dismiss(animated: true)
|
|
xcbSession?.complete(with: .cancel)
|
|
}
|
|
|
|
override func canPaste(_ itemProviders: [NSItemProvider]) -> Bool {
|
|
return composeAttachmentsViewController.canPaste(itemProviders)
|
|
}
|
|
|
|
override func paste(itemProviders: [NSItemProvider]) {
|
|
composeAttachmentsViewController.paste(itemProviders: itemProviders)
|
|
}
|
|
|
|
// MARK: - Interaction
|
|
|
|
@objc func showSaveAndClosePrompt() {
|
|
guard statusTextView.text.trimmingCharacters(in: .whitespacesAndNewlines) != initialText else {
|
|
close()
|
|
return
|
|
}
|
|
|
|
if Preferences.shared.automaticallySaveDrafts {
|
|
saveDraft()
|
|
close()
|
|
return
|
|
}
|
|
|
|
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
|
alert.addAction(UIAlertAction(title: "Save draft", style: .default, handler: { (_) in
|
|
self.saveDraft()
|
|
self.close()
|
|
}))
|
|
alert.addAction(UIAlertAction(title: "Delete draft", style: .destructive, handler: { (_) in
|
|
if let currentDraft = self.currentDraft {
|
|
DraftsManager.shared.remove(currentDraft)
|
|
}
|
|
self.close()
|
|
}))
|
|
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
|
present(alert, animated: true)
|
|
}
|
|
|
|
@objc func contentWarningButtonPressed() {
|
|
contentWarningEnabled = !contentWarningEnabled
|
|
if contentWarningEnabled {
|
|
contentWarningTextField.becomeFirstResponder()
|
|
} else {
|
|
statusTextView.becomeFirstResponder()
|
|
}
|
|
}
|
|
|
|
@objc func contentWarningTextFieldDidChange() {
|
|
updateCharactersRemaining()
|
|
updateHasChanges()
|
|
}
|
|
|
|
@objc func visibilityButtonPressed() {
|
|
let alertController = UIAlertController(currentVisibility: self.visibility) { (visibility) in
|
|
guard let visibility = visibility else { return }
|
|
self.visibility = visibility
|
|
}
|
|
present(alertController, animated: true)
|
|
}
|
|
|
|
@objc func formatButtonPressed(_ button: UIBarButtonItem) {
|
|
guard statusTextView.isFirstResponder else {
|
|
return
|
|
}
|
|
|
|
let format = StatusFormat.allCases[button.tag]
|
|
guard let insertionResult = format.insertionResult else {
|
|
return
|
|
}
|
|
|
|
let currentSelectedRange = statusTextView.selectedRange
|
|
if currentSelectedRange.length == 0 {
|
|
statusTextView.insertText(insertionResult.prefix + insertionResult.suffix)
|
|
statusTextView.selectedRange = NSRange(location: currentSelectedRange.location + insertionResult.insertionPoint, length: 0)
|
|
} else {
|
|
let start = statusTextView.text.index(statusTextView.text.startIndex, offsetBy: currentSelectedRange.lowerBound)
|
|
let end = statusTextView.text.index(statusTextView.text.startIndex, offsetBy: currentSelectedRange.upperBound)
|
|
let selectedText = statusTextView.text[start..<end]
|
|
statusTextView.insertText(String(insertionResult.prefix + selectedText + insertionResult.suffix))
|
|
statusTextView.selectedRange = NSRange(location: currentSelectedRange.location + insertionResult.prefix.count, length: currentSelectedRange.length)
|
|
}
|
|
}
|
|
|
|
@objc func draftsButtonPressed() {
|
|
let draftsVC = DraftsTableViewController(account: mastodonController.accountInfo!)
|
|
draftsVC.delegate = self
|
|
present(UINavigationController(rootViewController: draftsVC), animated: true)
|
|
}
|
|
|
|
@objc func postButtonPressed() {
|
|
guard let text = statusTextView.text,
|
|
!text.isEmpty else { return }
|
|
|
|
// save a draft before posting the status, so if a crash occurs during posting, the status won't be lost
|
|
saveDraft()
|
|
|
|
// disable post button while sending post request
|
|
compositionState.formUnion(.currentlyPosting)
|
|
|
|
let contentWarning: String?
|
|
if contentWarningEnabled, let cwText = contentWarningTextField.text, !cwText.isEmpty {
|
|
contentWarning = cwText
|
|
} else {
|
|
contentWarning = nil
|
|
}
|
|
let sensitive = contentWarning != nil
|
|
let visibility = self.visibility!
|
|
|
|
postProgressView.steps = 2 + (composeAttachmentsViewController.attachments.count * 2) // 2 steps (request data, then upload) for each attachment
|
|
postProgressView.currentStep = 1
|
|
|
|
composeAttachmentsViewController.uploadAll(stepProgress: postProgressView.step) { (success, uploadedAttachments) in
|
|
guard success else { return }
|
|
|
|
let request = Client.createStatus(text: text,
|
|
contentType: Preferences.shared.statusContentType,
|
|
inReplyTo: self.inReplyToID,
|
|
media: uploadedAttachments,
|
|
sensitive: sensitive,
|
|
spoilerText: contentWarning,
|
|
visibility: visibility,
|
|
language: nil)
|
|
self.mastodonController.run(request) { (response) in
|
|
guard case let .success(status, _) = response else { fatalError() }
|
|
self.postedStatus = status
|
|
self.mastodonController.cache.add(status: status)
|
|
|
|
if let draft = self.currentDraft {
|
|
DraftsManager.shared.remove(draft)
|
|
}
|
|
|
|
DispatchQueue.main.async {
|
|
self.postProgressView.step()
|
|
self.dismiss(animated: true)
|
|
|
|
// todo: this doesn't work
|
|
let conversationVC = ConversationTableViewController(for: status.id, mastodonController: self.mastodonController)
|
|
self.show(conversationVC, sender: self)
|
|
|
|
self.xcbSession?.complete(with: .success, additionalData: [
|
|
"statusURL": status.url?.absoluteString,
|
|
"statusURI": status.uri
|
|
])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
extension ComposeViewController: UIScrollViewDelegate {
|
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
|
guard let replyView = replyView else { return }
|
|
|
|
var constant: CGFloat = 8
|
|
|
|
if scrollView.contentOffset.y < 0 {
|
|
constant -= scrollView.contentOffset.y
|
|
replyAvatarImageViewTopConstraint?.constant = 8 - scrollView.contentOffset.y
|
|
} else if scrollView.contentOffset.y > replyView.frame.height - replyView.avatarImageView.frame.height - 16 {
|
|
constant += replyView.frame.height - replyView.avatarImageView.frame.height - 16 - scrollView.contentOffset.y
|
|
}
|
|
|
|
replyAvatarImageViewTopConstraint?.constant = constant
|
|
}
|
|
}
|
|
|
|
extension ComposeViewController: UITextViewDelegate {
|
|
func textViewDidChange(_ textView: UITextView) {
|
|
updateCharactersRemaining()
|
|
updatePlaceholder()
|
|
updateHasChanges()
|
|
}
|
|
}
|
|
|
|
extension ComposeViewController: ComposeAttachmentsViewControllerDelegate {
|
|
func composeSelectedAttachmentsDidChange() {
|
|
currentDraft?.attachments = composeAttachmentsViewController.attachments
|
|
}
|
|
|
|
func composeRequiresAttachmentDescriptionsDidChange() {
|
|
if composeAttachmentsViewController.requiresAttachmentDescriptions {
|
|
compositionState.formUnion(.requiresAttachmentDescriptions)
|
|
} else {
|
|
compositionState.subtract(.requiresAttachmentDescriptions)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension ComposeViewController: DraftsTableViewControllerDelegate {
|
|
func draftSelectionCanceled() {
|
|
}
|
|
|
|
func shouldSelectDraft(_ draft: DraftsManager.Draft, completion: @escaping (Bool) -> Void) {
|
|
if draft.inReplyToID != self.inReplyToID, hasChanges {
|
|
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) {
|
|
if hasChanges {
|
|
saveDraft()
|
|
}
|
|
|
|
self.currentDraft = draft
|
|
|
|
inReplyToID = draft.inReplyToID
|
|
updateInReplyTo()
|
|
|
|
statusTextView.text = draft.text
|
|
contentWarningEnabled = draft.contentWarning != nil
|
|
contentWarningTextField.text = draft.contentWarning
|
|
|
|
updatePlaceholder()
|
|
updateCharactersRemaining()
|
|
|
|
composeAttachmentsViewController.setAttachments(draft.attachments)
|
|
}
|
|
|
|
func draftSelectionCompleted() {
|
|
// todo: I don't think this can actually happen any more?
|
|
// check that all the assets from the draft have been added
|
|
if let currentDraft = currentDraft, composeAttachmentsViewController.attachments.count < currentDraft.attachments.count {
|
|
// some of the assets in the draft weren't loaded, so notify the user
|
|
|
|
let difference = currentDraft.attachments.count - composeAttachmentsViewController.attachments.count
|
|
// todo: localize me
|
|
let suffix = difference == 1 ? "" : "s"
|
|
let verb = difference == 1 ? "was" : "were"
|
|
let alertController = UIAlertController(title: "Missing Attachments", message: "\(difference) attachment\(suffix) \(verb) removed from the Photos Library and could not be loaded.", preferredStyle: .alert)
|
|
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
|
|
|
|
present(alertController, animated: true)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension ComposeViewController: UIAdaptivePresentationControllerDelegate {
|
|
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
|
|
return Preferences.shared.automaticallySaveDrafts || !hasChanges
|
|
}
|
|
|
|
func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
|
|
showSaveAndClosePrompt()
|
|
}
|
|
|
|
// when the compose screen is dismissed interactively, close() isn't called, so we make sure to
|
|
// complete the X-Callback-URL session and save the draft is automatic saving is enabled
|
|
// (if automatic saving is off, the draft will get saved/discarded by the user when didAttemptToDismiss is called
|
|
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
|
|
if Preferences.shared.automaticallySaveDrafts {
|
|
saveDraft()
|
|
}
|
|
xcbSession?.complete(with: .cancel)
|
|
}
|
|
}
|
|
|