266 lines
10 KiB
Swift
266 lines
10 KiB
Swift
//
|
|
// ComposeAttachmentsViewController.swift
|
|
// Tusker
|
|
//
|
|
// Created by Shadowfacts on 3/11/20.
|
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import Pachyderm
|
|
|
|
protocol ComposeAttachmentsViewControllerDelegate: class {
|
|
func composeSelectedAttachmentsDidChange()
|
|
func composeRequiresAttachmentDescriptionsDidChange()
|
|
}
|
|
|
|
class ComposeAttachmentsViewController: UITableViewController {
|
|
|
|
weak var mastodonController: MastodonController!
|
|
weak var delegate: ComposeAttachmentsViewControllerDelegate?
|
|
|
|
private var heightConstraint: NSLayoutConstraint!
|
|
|
|
var attachments: [CompositionAttachment] = [] {
|
|
didSet {
|
|
delegate?.composeSelectedAttachmentsDidChange()
|
|
updateAddAttachmentsButtonEnabled()
|
|
}
|
|
}
|
|
|
|
var requiresAttachmentDescriptions: Bool {
|
|
if Preferences.shared.requireAttachmentDescriptions {
|
|
return !attachments.allSatisfy { $0.description.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
init(attachments: [CompositionAttachment], mastodonController: MastodonController) {
|
|
self.attachments = attachments
|
|
self.mastodonController = mastodonController
|
|
super.init(style: .plain)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
tableView.rowHeight = UITableView.automaticDimension
|
|
tableView.estimatedRowHeight = 96
|
|
|
|
tableView.register(UINib(nibName: "AddAttachmentTableViewCell", bundle: .main), forCellReuseIdentifier: "addAttachment")
|
|
tableView.register(UINib(nibName: "ComposeAttachmentTableViewCell", bundle: .main), forCellReuseIdentifier: "composeAttachment")
|
|
|
|
heightConstraint = tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height)
|
|
heightConstraint.isActive = true
|
|
}
|
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
super.viewWillAppear(animated)
|
|
updateHeightConstraint()
|
|
}
|
|
|
|
func setAttachments(_ attachments: [CompositionAttachment]) {
|
|
self.attachments = attachments
|
|
tableView.reloadData()
|
|
updateHeightConstraint()
|
|
delegate?.composeRequiresAttachmentDescriptionsDidChange()
|
|
}
|
|
|
|
private func updateHeightConstraint() {
|
|
heightConstraint.constant = tableView.contentSize.height
|
|
}
|
|
|
|
private func isAddAttachmentsButtonEnabled() -> Bool {
|
|
switch mastodonController.instance.instanceType {
|
|
case .pleroma:
|
|
return true
|
|
case .mastodon:
|
|
return !attachments.contains(where: { $0.data.type == .video }) && attachments.count < 4
|
|
}
|
|
}
|
|
|
|
private func updateAddAttachmentsButtonEnabled() {
|
|
let cell = tableView.cellForRow(at: IndexPath(row: 0, section: 1)) as! AddAttachmentTableViewCell
|
|
cell.setEnabled(isAddAttachmentsButtonEnabled())
|
|
}
|
|
|
|
func uploadAll(stepProgress: @escaping () -> Void, completion: @escaping (_ success: Bool, _ uploadedAttachments: [Attachment]) -> Void) {
|
|
let group = DispatchGroup()
|
|
|
|
var anyFailed = false
|
|
var uploadedAttachments: [Result<Attachment, Error>?] = []
|
|
|
|
for (index, compAttachment) in attachments.enumerated() {
|
|
group.enter()
|
|
|
|
uploadedAttachments.append(nil)
|
|
|
|
compAttachment.data.getData { (data, mimeType) in
|
|
stepProgress()
|
|
|
|
let formAttachment = FormAttachment(mimeType: mimeType, data: data, fileName: "file")
|
|
let request = Client.upload(attachment: formAttachment, description: compAttachment.description)
|
|
self.mastodonController.run(request) { (response) in
|
|
switch response {
|
|
case let .failure(error):
|
|
uploadedAttachments[index] = .failure(error)
|
|
anyFailed = true
|
|
case let .success(attachment, _):
|
|
uploadedAttachments[index] = .success(attachment)
|
|
}
|
|
|
|
stepProgress()
|
|
group.leave()
|
|
}
|
|
}
|
|
}
|
|
|
|
group.notify(queue: .main) {
|
|
if anyFailed {
|
|
let errors: [(Int, Error)] = uploadedAttachments.enumerated().compactMap { (index, result) in
|
|
switch result {
|
|
case let .failure(error):
|
|
return (index, error)
|
|
default:
|
|
return nil
|
|
}
|
|
|
|
}
|
|
let title: String
|
|
var message: String
|
|
if errors.count == 1 {
|
|
title = NSLocalizedString("Could not upload attachment", comment: "single attachment upload failed alert title")
|
|
message = errors[0].1.localizedDescription
|
|
} else {
|
|
title = NSLocalizedString("Could not upload the following attachments", comment: "multiple attachment upload failures alert title")
|
|
message = ""
|
|
for (index, error) in errors {
|
|
message.append("Attachment \(index + 1): \(error.localizedDescription)")
|
|
}
|
|
}
|
|
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
|
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (_) in
|
|
completion(false, [])
|
|
}))
|
|
} else {
|
|
let uploadedAttachments: [Attachment] = uploadedAttachments.compactMap {
|
|
switch $0 {
|
|
case let .success(attachment):
|
|
return attachment
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
completion(true, uploadedAttachments)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Table view data source
|
|
|
|
override func numberOfSections(in tableView: UITableView) -> Int {
|
|
return 2
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
switch section {
|
|
case 0:
|
|
return attachments.count
|
|
case 1:
|
|
return 1
|
|
default:
|
|
fatalError("invalid section \(section)")
|
|
}
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
switch indexPath.section {
|
|
case 0:
|
|
let attachment = attachments[indexPath.row]
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: "composeAttachment", for: indexPath) as! ComposeAttachmentTableViewCell
|
|
cell.delegate = self
|
|
cell.updateUI(for: attachment)
|
|
return cell
|
|
case 1:
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: "addAttachment", for: indexPath) as! AddAttachmentTableViewCell
|
|
cell.setEnabled(isAddAttachmentsButtonEnabled())
|
|
return cell
|
|
default:
|
|
fatalError("invalid section \(indexPath.section)")
|
|
}
|
|
}
|
|
|
|
// MARK: Table view delegate
|
|
|
|
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
|
if indexPath.section == 1, isAddAttachmentsButtonEnabled() {
|
|
return indexPath
|
|
}
|
|
return nil
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
tableView.deselectRow(at: indexPath, animated: true)
|
|
|
|
if indexPath.section == 1 {
|
|
addAttachmentPressed()
|
|
}
|
|
}
|
|
|
|
func addAttachmentPressed() {
|
|
let sheetContainer = AssetPickerSheetContainerViewController()
|
|
sheetContainer.assetPicker.assetPickerDelegate = self
|
|
present(sheetContainer, animated: true)
|
|
}
|
|
|
|
}
|
|
|
|
extension ComposeAttachmentsViewController: AssetPickerViewControllerDelegate {
|
|
func assetPicker(_ assetPicker: AssetPickerViewController, shouldAllowAssetOfType type: CompositionAttachmentData.AttachmentType) -> Bool {
|
|
switch mastodonController.instance.instanceType {
|
|
case .pleroma:
|
|
return true
|
|
case .mastodon:
|
|
if (type == .video && attachments.count > 0) ||
|
|
attachments.contains(where: { $0.data.type == .video }) ||
|
|
assetPicker.currentCollectionSelectedAssets.contains(where: { $0.type == .video }) {
|
|
return false
|
|
}
|
|
return attachments.count + assetPicker.currentCollectionSelectedAssets.count < 4
|
|
}
|
|
}
|
|
|
|
func assetPicker(_ assetPicker: AssetPickerViewController, didSelectAttachments attachments: [CompositionAttachmentData]) {
|
|
let attachments = attachments.map {
|
|
CompositionAttachment(data: $0)
|
|
}
|
|
let indexPaths = attachments.indices.map { IndexPath(row: $0 + self.attachments.count, section: 0) }
|
|
self.attachments.append(contentsOf: attachments)
|
|
tableView.insertRows(at: indexPaths, with: .automatic)
|
|
updateHeightConstraint()
|
|
}
|
|
}
|
|
|
|
extension ComposeAttachmentsViewController: ComposeAttachmentTableViewCellDelegate {
|
|
func removeAttachment(_ cell: ComposeAttachmentTableViewCell) {
|
|
guard let indexPath = tableView.indexPath(for: cell) else { return }
|
|
attachments.remove(at: indexPath.row)
|
|
tableView.performBatchUpdates({
|
|
tableView.deleteRows(at: [indexPath], with: .automatic)
|
|
}, completion: { (_) in
|
|
// when removing cells, we don't trigger the container height update until after the animation has completed
|
|
// otherwise, during the animation, the height is too short and the last row briefly disappears
|
|
self.updateHeightConstraint()
|
|
})
|
|
}
|
|
|
|
func attachmentDescriptionChanged(_ cell: ComposeAttachmentTableViewCell) {
|
|
delegate?.composeRequiresAttachmentDescriptionsDidChange()
|
|
}
|
|
}
|