forked from shadowfacts/Tusker
278 lines
12 KiB
Swift
278 lines
12 KiB
Swift
//
|
|
// ComposeViewController.swift
|
|
// Tusker
|
|
//
|
|
// Created by Shadowfacts on 8/28/18.
|
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import Pachyderm
|
|
|
|
class ComposeViewController: UIViewController {
|
|
|
|
static func create(inReplyTo: Status? = nil, mentioning: Account? = nil) -> UIViewController {
|
|
guard let navigationController = UIStoryboard(name: "Compose", bundle: nil).instantiateInitialViewController() as? UINavigationController,
|
|
let composeVC = navigationController.topViewController as? ComposeViewController else { fatalError() }
|
|
composeVC.inReplyTo = inReplyTo
|
|
composeVC.mentioning = mentioning
|
|
return navigationController
|
|
}
|
|
|
|
@IBOutlet weak var scrollView: UIScrollView!
|
|
@IBOutlet weak var inReplyToContainerView: UIView!
|
|
@IBOutlet weak var inReplyToAvatarImageView: UIImageView!
|
|
@IBOutlet weak var inReplyToDisplayNameLabel: UILabel!
|
|
@IBOutlet weak var inReplyToUsernameLabel: UILabel!
|
|
@IBOutlet weak var inReplyToContentLabel: StatusContentLabel!
|
|
@IBOutlet weak var inReplyToLabel: UILabel!
|
|
@IBOutlet weak var statusTextView: UITextView!
|
|
@IBOutlet weak var visibilityButton: UIButton!
|
|
@IBOutlet weak var postButton: UIButton!
|
|
@IBOutlet weak var contentWarningTextField: UITextField!
|
|
@IBOutlet weak var mediaStackView: UIStackView!
|
|
@IBOutlet weak var paddingView: UIView!
|
|
@IBOutlet weak var progressView: SteppedProgressView!
|
|
|
|
var scrolled = false
|
|
|
|
var inReplyTo: Status?
|
|
var mentioning: Account?
|
|
|
|
var contentWarning = false {
|
|
didSet {
|
|
contentWarningTextField.isHidden = !contentWarning
|
|
}
|
|
}
|
|
var visibility = Preferences.shared.defaultPostVisibility {
|
|
didSet {
|
|
visibilityButton.setTitle(visibility.displayName, for: .normal)
|
|
}
|
|
}
|
|
|
|
var status: Status?
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
statusTextView.placeholder = "What is on your mind?"
|
|
statusTextView.layer.cornerRadius = 5
|
|
statusTextView.layer.masksToBounds = true
|
|
visibilityButton.setTitle(visibility.displayName, for: .normal)
|
|
contentWarningTextField.delegate = self
|
|
|
|
let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 30))
|
|
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
|
|
let done = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(keyboardDoneButtonPressed))
|
|
toolbar.setItems([flexSpace, done], animated: false)
|
|
toolbar.sizeToFit()
|
|
statusTextView.inputAccessoryView = toolbar
|
|
|
|
if let inReplyTo = inReplyTo {
|
|
inReplyToDisplayNameLabel.text = inReplyTo.account.realDisplayName
|
|
inReplyToUsernameLabel.text = "@\(inReplyTo.account.username)"
|
|
inReplyToContentLabel.status = inReplyTo
|
|
inReplyToAvatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: inReplyToAvatarImageView)
|
|
inReplyToAvatarImageView.layer.masksToBounds = true
|
|
inReplyToAvatarImageView.image = nil
|
|
AvatarCache.shared.get(inReplyTo.account.avatar) { image in
|
|
DispatchQueue.main.async {
|
|
self.inReplyToAvatarImageView.image = image
|
|
}
|
|
}
|
|
inReplyToLabel.text = "In reply to \(inReplyTo.account.realDisplayName)"
|
|
if inReplyTo.account != MastodonController.shared.account {
|
|
statusTextView.text = "@\(inReplyTo.account.acct) "
|
|
}
|
|
statusTextView.text += inReplyTo.mentions.filter({ $0.id != MastodonController.shared.account.id }).map({ "@\($0.acct) " }).joined()
|
|
statusTextView.textViewDidChange(statusTextView)
|
|
contentWarning = inReplyTo.sensitive
|
|
contentWarningTextField.text = inReplyTo.spoilerText
|
|
visibility = inReplyTo.visibility
|
|
} else {
|
|
inReplyToLabel.isHidden = true
|
|
inReplyToContainerView.isHidden = true
|
|
}
|
|
|
|
if let mentioning = mentioning {
|
|
statusTextView.text += "@\(mentioning.acct) "
|
|
statusTextView.textViewDidChange(statusTextView)
|
|
}
|
|
|
|
progressView.progress = 0
|
|
}
|
|
|
|
override func viewDidLayoutSubviews() {
|
|
if inReplyTo != nil && !scrolled {
|
|
scrollView.contentOffset = CGPoint(x: 0, y: inReplyToContainerView.bounds.height - 44)
|
|
scrolled = true
|
|
}
|
|
}
|
|
|
|
func addMedia(for image: UIImage) {
|
|
let mediaView = ComposeMediaView(image: image)
|
|
mediaView.delegate = self
|
|
mediaStackView.addArrangedSubview(mediaView)
|
|
}
|
|
|
|
// MARK: - Navigation
|
|
|
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
|
statusTextView.resignFirstResponder()
|
|
contentWarningTextField.resignFirstResponder()
|
|
|
|
if segue.identifier == "postComplete" {
|
|
guard let status = status else { fatalError("postComplete segue can't occur without Status") }
|
|
guard let dest = segue.destination as? MainTabBarViewController,
|
|
let navController = dest.selectedViewController as? UINavigationController,
|
|
let topVC = navController.topViewController as? StatusTableViewCellDelegate else { return }
|
|
topVC.selected(status: status)
|
|
}
|
|
}
|
|
|
|
// MARK: - Interaction
|
|
|
|
@IBAction func visibilityPressed(_ sender: Any) {
|
|
let alertController = UIAlertController(title: "Post Visibility", message: nil, preferredStyle: .actionSheet)
|
|
for visibility in Status.Visibility.allCases {
|
|
let action = UIAlertAction(title: visibility.displayName, style: .default, handler: { _ in
|
|
UIView.performWithoutAnimation {
|
|
self.visibility = visibility
|
|
self.visibilityButton.layoutIfNeeded()
|
|
}
|
|
})
|
|
if visibility == self.visibility {
|
|
action.setValue(true, forKey: "checked")
|
|
}
|
|
alertController.addAction(action)
|
|
}
|
|
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
|
present(alertController, animated: true)
|
|
}
|
|
|
|
@IBAction func contentWarningPressed(_ sender: Any) {
|
|
contentWarning = !contentWarning
|
|
}
|
|
|
|
@IBAction func mediaPressed(_ sender: Any) {
|
|
let imagePicker = UIImagePickerController()
|
|
imagePicker.delegate = self
|
|
|
|
let alertController = UIAlertController(title: "Choose Image Source", message: nil, preferredStyle: .actionSheet)
|
|
if UIImagePickerController.isSourceTypeAvailable(.camera) {
|
|
alertController.addAction(UIAlertAction(title: "Camera", style: .default, handler: { _ in
|
|
imagePicker.sourceType = .camera
|
|
self.present(imagePicker, animated: true)
|
|
}))
|
|
}
|
|
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
|
|
alertController.addAction(UIAlertAction(title: "Photo Library", style: .default, handler: { __ in
|
|
imagePicker.sourceType = .photoLibrary
|
|
self.present(imagePicker, animated: true)
|
|
}))
|
|
}
|
|
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
|
present(alertController, animated: true)
|
|
}
|
|
|
|
@IBAction func postPressed(_ sender: Any) {
|
|
guard let text = statusTextView.text,
|
|
!text.isEmpty else { return }
|
|
|
|
postButton.isEnabled = false
|
|
|
|
let contentWarning: String?
|
|
if self.contentWarning,
|
|
let text = contentWarningTextField.text,
|
|
!text.isEmpty {
|
|
contentWarning = text
|
|
} else {
|
|
contentWarning = nil
|
|
}
|
|
let sensitive = contentWarning != nil
|
|
|
|
let visibility = self.visibility
|
|
|
|
var attachments: [Attachment?] = []
|
|
let group = DispatchGroup()
|
|
for view in mediaStackView.arrangedSubviews {
|
|
guard let mediaView = view as? ComposeMediaView,
|
|
let image = mediaView.image,
|
|
let data = image.pngData() else { continue }
|
|
let index = attachments.count
|
|
attachments.append(nil)
|
|
progressView.steps += 1
|
|
group.enter()
|
|
let request = MastodonController.shared.client.upload(attachment: FormAttachment(pngData: data), description: mediaView.mediaDescription)
|
|
MastodonController.shared.client.run(request) { response in
|
|
guard case let .success(attachment, _) = response else { fatalError() }
|
|
attachments[index] = attachment
|
|
self.progressView.step()
|
|
group.leave()
|
|
}
|
|
}
|
|
|
|
progressView.steps = 2 + attachments.count
|
|
progressView.currentStep = 1
|
|
|
|
group.notify(queue: .main) {
|
|
let attachments = attachments.compactMap { $0 }
|
|
|
|
let request = MastodonController.shared.client.createStatus(text: text,
|
|
inReplyTo: self.inReplyTo,
|
|
media: attachments,
|
|
sensitive: sensitive,
|
|
spoilerText: contentWarning,
|
|
visiblity: visibility)
|
|
MastodonController.shared.client.run(request) { response in
|
|
guard case let .success(status, _) = response else { fatalError() }
|
|
self.status = status
|
|
DispatchQueue.main.async {
|
|
self.progressView.step()
|
|
self.performSegue(withIdentifier: "postComplete", sender: self)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc func imagePressed(_ gesture: UITapGestureRecognizer) {
|
|
gesture.view!.superview!.removeFromSuperview()
|
|
}
|
|
|
|
@objc func keyboardDoneButtonPressed() {
|
|
statusTextView.endEditing(false)
|
|
}
|
|
|
|
}
|
|
|
|
extension ComposeViewController: UITextFieldDelegate {
|
|
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
|
textField.endEditing(false)
|
|
return true
|
|
}
|
|
}
|
|
|
|
extension ComposeViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
|
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
|
if let selectedImage = info[.originalImage] as? UIImage {
|
|
addMedia(for: selectedImage)
|
|
dismiss(animated: true)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension ComposeViewController: ComposeMediaViewDelegate {
|
|
func editDescription(for media: ComposeMediaView) {
|
|
let alertController = UIAlertController(title: "Media Description", message: nil, preferredStyle: .alert)
|
|
alertController.addTextField { textField in
|
|
textField.text = media.mediaDescription
|
|
}
|
|
alertController.addAction(UIAlertAction(title: "Confirm", style: .default, handler: { _ in
|
|
let description = alertController.textFields![0].text
|
|
media.mediaDescription = description
|
|
}))
|
|
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
|
present(alertController, animated: true)
|
|
}
|
|
}
|