123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972 |
- //
- // SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
- // SPDX-License-Identifier: GPL-3.0-or-later
- //
- import Foundation
- import NextcloudKit
- import QuickLook
- import SwiftyAttributes
- import TOCropViewController
- import AVFoundation
- @objc public protocol ShareConfirmationViewControllerDelegate {
- @objc func shareConfirmationViewControllerDidFailed(_ viewController: ShareConfirmationViewController)
- @objc func shareConfirmationViewControllerDidFinish(_ viewController: ShareConfirmationViewController)
- }
- @objcMembers public class ShareConfirmationViewController: InputbarViewController,
- NKCommonDelegate,
- ShareItemControllerDelegate,
- UIImagePickerControllerDelegate,
- UIDocumentPickerDelegate,
- UINavigationControllerDelegate,
- UICollectionViewDelegateFlowLayout,
- TOCropViewControllerDelegate,
- QLPreviewControllerDataSource,
- QLPreviewControllerDelegate {
- // MARK: - Public var
- public var account: TalkAccount
- public var isModal: Bool = false
- public var forwardingMessage: Bool = false
- public weak var delegate: ShareConfirmationViewControllerDelegate?
- public lazy var shareItemController: ShareItemController = {
- let controller = ShareItemController()
- controller.delegate = self
- return controller
- }()
- // MARK: - Private var
- private var serverCapabilities: ServerCapabilities
- private var shareType: ShareConfirmationType = .item
- private var shareContentView = UIView()
- private var shareSilently = false
- private var imagePicker: UIImagePickerController?
- private var hud: MBProgressHUD?
- private var objectShareMessage: NCChatMessage?
- private var uploadGroup = DispatchGroup()
- private var uploadFailed = false
- private var uploadErrors: [String] = []
- private var uploadSuccess: [ShareItem] = []
- private enum ShareConfirmationType {
- case text
- case item
- case objectShare
- }
- // MARK: - UI Controls
- private lazy var sendButton: UIBarButtonItem = {
- let sendButton = UIBarButtonItem(title: NSLocalizedString("Send", comment: ""), style: .done, target: self, action: #selector(sendButtonPressed))
- sendButton.accessibilityHint = NSLocalizedString("Double tap to share with selected conversations", comment: "")
- return sendButton
- }()
- private lazy var sharingIndicatorView: UIActivityIndicatorView = {
- let indicator = UIActivityIndicatorView()
- indicator.color = NCAppBranding.themeTextColor()
- return indicator
- }()
- private lazy var toLabel: UILabel = {
- var label = UILabel()
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
- private lazy var toLabelView: UIView = {
- let view = UIView()
- view.translatesAutoresizingMaskIntoConstraints = false
- view.backgroundColor = .secondarySystemBackground
- view.addSubview(self.toLabel)
- NSLayoutConstraint.activate([
- self.toLabel.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 20),
- self.toLabel.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: -20),
- self.toLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
- self.toLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
- ])
- return view
- }()
- private lazy var itemToolbar: UIToolbar = {
- let toolbar = UIToolbar(frame: .init(x: 0, y: 0, width: 100, height: 44))
- let flexibleSpace = UIBarButtonItem(systemItem: .flexibleSpace)
- toolbar.barTintColor = .systemBackground
- toolbar.isTranslucent = false
- toolbar.setItems([removeItemButton, flexibleSpace, cropItemButton, previewItemButton, addItemButton], animated: false)
- toolbar.translatesAutoresizingMaskIntoConstraints = false
- return toolbar
- }()
- private lazy var removeItemButton: UIBarButtonItem = {
- let button = UIBarButtonItem(image: .init(systemName: "trash"))
- button.width = 56
- button.target = self
- button.action = #selector(removeItemButtonPressed)
- return button
- }()
- private lazy var cropItemButton: UIBarButtonItem = {
- let button = UIBarButtonItem(image: .init(systemName: "crop.rotate"))
- button.width = 56
- button.target = self
- button.action = #selector(cropItemButtonPressed)
- return button
- }()
- private lazy var previewItemButton: UIBarButtonItem = {
- let button = UIBarButtonItem(image: .init(systemName: "eye"))
- button.width = 56
- button.target = self
- button.action = #selector(previewItemButtonPressed)
- return button
- }()
- private lazy var addItemButton: UIBarButtonItem = {
- let button = UIBarButtonItem(image: .init(systemName: "plus"))
- button.width = 56
- var items: [UIAction] = []
- let cameraAction = UIAction(title: NSLocalizedString("Camera", comment: ""), image: UIImage(systemName: "camera")) { [unowned self] _ in
- self.textView.resignFirstResponder()
- self.checkAndPresentCamera()
- }
- let photoLibraryAction = UIAction(title: NSLocalizedString("Photo Library", comment: ""), image: UIImage(systemName: "photo")) { [unowned self] _ in
- self.textView.resignFirstResponder()
- self.presentPhotoLibrary()
- }
- let filesAction = UIAction(title: NSLocalizedString("Files", comment: ""), image: UIImage(systemName: "doc")) { [unowned self] _ in
- self.textView.resignFirstResponder()
- self.presentDocumentPicker()
- }
- #if !APP_EXTENSION
- // Camera access is not available in app extensions
- // https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionOverview.html
- if UIImagePickerController.isSourceTypeAvailable(.camera) {
- items.append(cameraAction)
- }
- #endif
- items.append(photoLibraryAction)
- items.append(filesAction)
- button.menu = UIMenu(children: items)
- return button
- }()
- private lazy var shareCollectionViewLayout: UICollectionViewFlowLayout = {
- // Make sure that we use a layout that invalidates itself when the bounds changed
- let layout = BoundsChangedFlowLayout()
- layout.scrollDirection = .horizontal
- layout.minimumLineSpacing = 0
- layout.minimumInteritemSpacing = 0
- return layout
- }()
- private lazy var shareCollectionView: UICollectionView = {
- let collectionView = UICollectionView(frame: .init(x: 0, y: 0, width: 10, height: 10), collectionViewLayout: self.shareCollectionViewLayout)
- collectionView.translatesAutoresizingMaskIntoConstraints = false
- collectionView.delegate = self
- collectionView.dataSource = self
- collectionView.isPagingEnabled = true
- collectionView.showsVerticalScrollIndicator = false
- return collectionView
- }()
- private lazy var shareTextView: UITextView = {
- let textView = UITextView()
- textView.font = .preferredFont(forTextStyle: .body)
- textView.translatesAutoresizingMaskIntoConstraints = false
- textView.isHidden = true
- return textView
- }()
- private lazy var pageControl: UIPageControl = {
- let pageControl = UIPageControl()
- pageControl.translatesAutoresizingMaskIntoConstraints = false
- pageControl.currentPageIndicatorTintColor = NCAppBranding.elementColor()
- pageControl.pageIndicatorTintColor = NCAppBranding.placeholderColor()
- pageControl.hidesForSinglePage = true
- pageControl.numberOfPages = 1
- pageControl.addTarget(self, action: #selector(pageControlValueChanged), for: .valueChanged)
- return pageControl
- }()
- // MARK: - Init.
- public init?(room: NCRoom, account: TalkAccount, serverCapabilities: ServerCapabilities) {
- self.account = account
- self.serverCapabilities = serverCapabilities
- super.init(for: room, withView: self.shareContentView)
- self.shareContentView.addSubview(self.toLabelView)
- NSLayoutConstraint.activate([
- self.toLabelView.leftAnchor.constraint(equalTo: self.shareContentView.safeAreaLayoutGuide.leftAnchor),
- self.toLabelView.rightAnchor.constraint(equalTo: self.shareContentView.safeAreaLayoutGuide.rightAnchor),
- self.toLabelView.topAnchor.constraint(equalTo: self.shareContentView.safeAreaLayoutGuide.topAnchor),
- self.toLabelView.heightAnchor.constraint(equalToConstant: 36)
- ])
- self.shareContentView.addSubview(self.shareTextView)
- NSLayoutConstraint.activate([
- self.shareTextView.leftAnchor.constraint(equalTo: self.shareContentView.safeAreaLayoutGuide.leftAnchor, constant: 20),
- self.shareTextView.rightAnchor.constraint(equalTo: self.shareContentView.safeAreaLayoutGuide.rightAnchor, constant: -20),
- self.shareTextView.topAnchor.constraint(equalTo: self.toLabelView.bottomAnchor, constant: 20),
- self.shareTextView.bottomAnchor.constraint(equalTo: self.shareContentView.safeAreaLayoutGuide.bottomAnchor, constant: -20)
- ])
- self.shareContentView.addSubview(self.itemToolbar)
- NSLayoutConstraint.activate([
- self.itemToolbar.leftAnchor.constraint(equalTo: self.shareContentView.safeAreaLayoutGuide.leftAnchor),
- self.itemToolbar.rightAnchor.constraint(equalTo: self.shareContentView.safeAreaLayoutGuide.rightAnchor),
- self.itemToolbar.topAnchor.constraint(equalTo: self.toLabelView.bottomAnchor),
- self.itemToolbar.heightAnchor.constraint(equalToConstant: 44)
- ])
- self.shareContentView.addSubview(self.shareCollectionView)
- self.shareContentView.addSubview(self.pageControl)
- NSLayoutConstraint.activate([
- self.shareCollectionView.leftAnchor.constraint(equalTo: self.shareContentView.safeAreaLayoutGuide.leftAnchor),
- self.shareCollectionView.rightAnchor.constraint(equalTo: self.shareContentView.safeAreaLayoutGuide.rightAnchor),
- self.shareCollectionView.topAnchor.constraint(equalTo: self.itemToolbar.bottomAnchor, constant: 8),
- self.shareCollectionView.bottomAnchor.constraint(equalTo: self.pageControl.topAnchor, constant: -8),
- self.pageControl.leftAnchor.constraint(equalTo: self.shareContentView.safeAreaLayoutGuide.leftAnchor),
- self.pageControl.rightAnchor.constraint(equalTo: self.shareContentView.safeAreaLayoutGuide.rightAnchor),
- self.pageControl.heightAnchor.constraint(equalToConstant: 26),
- self.pageControl.bottomAnchor.constraint(equalTo: self.textInputbar.topAnchor)
- ])
- }
- required init?(coder decoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
- public func shareText(_ sharedText: String) {
- self.shareType = .text
- DispatchQueue.main.async {
- self.setTextInputbarHidden(true, animated: false)
- self.shareCollectionView.isHidden = true
- self.itemToolbar.isHidden = true
- self.shareTextView.isHidden = false
- self.shareTextView.text = sharedText
- // When an item of type "public.url" or "public.plain-text" is shared,
- // we switch to text-sharing after viewWillAppear, so we need to add the sendButton here as well
- self.navigationItem.rightBarButtonItem = self.sendButton
- self.navigationItem.rightBarButtonItem?.tintColor = NCAppBranding.themeTextColor()
- }
- }
- public func shareObjectShareMessage(_ objectShareMessage: NCChatMessage) {
- self.shareType = .objectShare
- DispatchQueue.main.async {
- self.setTextInputbarHidden(true, animated: false)
- self.shareCollectionView.isHidden = true
- self.itemToolbar.isHidden = true
- self.shareTextView.isHidden = false
- self.shareTextView.isUserInteractionEnabled = false
- self.shareTextView.text = objectShareMessage.parsedMessage().string
- self.objectShareMessage = objectShareMessage
- }
- }
- // MARK: - View lifecycle
- public override func viewDidLoad() {
- super.viewDidLoad()
- // Configure communication lib
- let userToken = NCKeyChainController.sharedInstance().token(forAccountId: self.account.accountId)
- let userAgent = "Mozilla/5.0 (iOS) Nextcloud-Talk v\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] ?? "Unknown")"
- NextcloudKit.shared.setup(account: self.account.accountId,
- user: self.account.user,
- userId: self.account.userId,
- password: userToken,
- urlBase: self.account.server,
- userAgent: userAgent,
- nextcloudVersion: self.serverCapabilities.versionMajor,
- delegate: self)
- let localizedToString = NSLocalizedString("To:", comment: "TRANSLATORS this is for sending something 'to' a user. E.g. 'To: John Doe'")
- let toString = localizedToString.withFont(.boldSystemFont(ofSize: 15)).withTextColor(.tertiaryLabel)
- let roomString = self.room.displayName.withFont(.systemFont(ofSize: 15)).withTextColor(.label)
- self.toLabel.attributedText = toString + NSAttributedString(string: " ") + roomString
- let bundle = Bundle(for: ShareConfirmationCollectionViewCell.self)
- self.shareCollectionView.register(UINib(nibName: kShareConfirmationTableCellNibName, bundle: bundle), forCellWithReuseIdentifier: kShareConfirmationCellIdentifier)
- self.shareCollectionView.delegate = self
- }
- public override func viewWillAppear(_ animated: Bool) {
- // Add the cancel button in viewWillAppear, so that the caller can change the isModal property after initialization
- if self.isModal {
- let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(self.cancelButtonPressed))
- cancelButton.accessibilityHint = NSLocalizedString("Double tap to dismiss sharing options", comment: "")
- self.navigationItem.leftBarButtonItem = cancelButton
- self.navigationItem.leftBarButtonItem?.tintColor = NCAppBranding.themeTextColor()
- }
- var captionAllowed = NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityMediaCaption, forAccountId: account.accountId)
- captionAllowed = captionAllowed && self.shareType == .item
- if !captionAllowed {
- self.navigationItem.rightBarButtonItem = self.sendButton
- self.navigationItem.rightBarButtonItem?.tintColor = NCAppBranding.themeTextColor()
- self.setTextInputbarHidden(true, animated: false)
- } else {
- let silentSendAction = UIAction(title: NSLocalizedString("Send without notification", comment: ""), image: UIImage(systemName: "bell.slash")) { [unowned self] _ in
- self.silentSendPressed()
- }
- self.rightButton.menu = UIMenu(children: [silentSendAction])
- }
- }
- public override func viewDidAppear(_ animated: Bool) {
- super.viewDidAppear(animated)
- if self.shareType == .text {
- // When we are sharing a text, we want to start editing right away
- self.shareTextView.becomeFirstResponder()
- }
- }
- public override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
- super.viewWillTransition(to: size, with: coordinator)
- if self.shareType == .text {
- return
- }
- self.shareCollectionView.isHidden = true
- // Invalidate layout to remove warning about item size must be less than UICollectionView
- self.shareCollectionView.collectionViewLayout.invalidateLayout()
- let currentItem = self.getCurrentShareItem()
- coordinator.animate { _ in
- // Invalidate the view now so cell size is correctly calculated
- // The size of the collection view is correct at this moment
- self.shareCollectionView.collectionViewLayout.invalidateLayout()
- } completion: { _ in
- // Scroll to the element and make collection view appear
- if let currentItem {
- self.scroll(to: currentItem, animated: false)
- }
- self.shareCollectionView.isHidden = false
- }
- }
- override func setTitleView() {
- // We don't want a titleView in this case
- }
- public override func canPressRightButton() -> Bool {
- // We want to allow sending pictures even when no text is entered
- return true
- }
- // MARK: - Button Actions
- func removeItemButtonPressed() {
- if let item = self.getCurrentShareItem() {
- self.shareItemController.remove(item)
- }
- }
- func cropItemButtonPressed() {
- if let item = self.getCurrentShareItem(),
- let image = self.shareItemController.getImageFrom(item) {
- let cropViewController = TOCropViewController(image: image)
- cropViewController.delegate = self
- self.present(cropViewController, animated: true)
- }
- }
- func previewItemButtonPressed() {
- self.previewCurrentItem()
- }
- func cancelButtonPressed() {
- self.delegate?.shareConfirmationViewControllerDidFinish(self)
- }
- func sendButtonPressed() {
- self.sendCurrent(silently: false)
- }
- public override func didPressRightButton(_ sender: Any?) {
- self.sendCurrent(silently: false)
- }
- func silentSendPressed() {
- self.sendCurrent(silently: true)
- }
- func sendCurrent(silently: Bool) {
- self.shareSilently = silently
- if self.shareType == .text {
- self.sendSharedText()
- } else if self.shareType == .objectShare {
- self.sendObjectShare()
- } else {
- self.uploadAndShareFiles()
- }
- self.startAnimatingSharingIndicator()
- }
- // MARK: - Add additional items
- func checkAndPresentCamera() {
- // https://stackoverflow.com/a/20464727/2512312
- let mediaType = AVMediaType.video
- let authStatus = AVCaptureDevice.authorizationStatus(for: mediaType)
- if authStatus == AVAuthorizationStatus.authorized {
- self.presentCamera()
- return
- } else if authStatus == AVAuthorizationStatus.notDetermined {
- AVCaptureDevice.requestAccess(for: mediaType, completionHandler: { (granted: Bool) in
- if granted {
- self.presentCamera()
- }
- })
- return
- }
- let alert = UIAlertController(title: NSLocalizedString("Could not access camera", comment: ""),
- message: NSLocalizedString("Camera access is not allowed. Check your settings.", comment: ""),
- preferredStyle: .alert)
- alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default))
- self.present(alert, animated: true)
- }
- func presentCamera() {
- DispatchQueue.main.async {
- self.imagePicker = UIImagePickerController()
- if let imagePicker = self.imagePicker,
- let sourceType = UIImagePickerController.availableMediaTypes(for: imagePicker.sourceType) {
- imagePicker.sourceType = .camera
- imagePicker.cameraFlashMode = UIImagePickerController.CameraFlashMode(rawValue: NCUserDefaults.preferredCameraFlashMode()) ?? .off
- imagePicker.mediaTypes = sourceType
- imagePicker.delegate = self
- self.present(imagePicker, animated: true)
- }
- }
- }
- func presentPhotoLibrary() {
- self.imagePicker = UIImagePickerController()
- if let imagePicker = self.imagePicker {
- imagePicker.sourceType = .photoLibrary
- imagePicker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary) ?? []
- imagePicker.delegate = self
- self.present(imagePicker, animated: true)
- }
- }
- func presentDocumentPicker() {
- DispatchQueue.main.async {
- let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.item], asCopy: true)
- documentPicker.delegate = self
- self.present(documentPicker, animated: true)
- }
- }
- // MARK: - Actions
- func sendSharedText() {
- NCAPIController.sharedInstance().sendChatMessage(self.shareTextView.text, toRoom: self.room.token, displayName: nil, replyTo: -1, referenceId: nil, silently: false, for: self.account) { error in
- if let error {
- NCUtils.log(String(format: "Failed to share text. Error: %@", error.localizedDescription))
- self.delegate?.shareConfirmationViewControllerDidFailed(self)
- } else {
- NCIntentController.sharedInstance().donateSendMessageIntent(for: self.room)
- self.delegate?.shareConfirmationViewControllerDidFinish(self)
- }
- self.stopAnimatingSharingIndicator()
- }
- }
- func sendObjectShare() {
- NCAPIController.sharedInstance().shareRichObject(self.objectShareMessage?.richObjectFromObjectShare, inRoom: self.room.token, for: self.account) { error in
- if let error {
- NCUtils.log(String(format: "Failed to share rich object. Error: %@", error.localizedDescription))
- self.delegate?.shareConfirmationViewControllerDidFailed(self)
- } else {
- NCIntentController.sharedInstance().donateSendMessageIntent(for: self.room)
- self.delegate?.shareConfirmationViewControllerDidFinish(self)
- }
- self.stopAnimatingSharingIndicator()
- }
- }
- func updateHudProgress() {
- guard let hud = self.hud else { return }
- DispatchQueue.main.async {
- var progress: CGFloat = 0.0
- var items = 0
- for shareItem in self.shareItemController.shareItems {
- progress += shareItem.uploadProgress
- items += 1
- }
- hud.progress = Float(progress / CGFloat(items))
- }
- }
- func uploadAndShareFiles() {
- // TODO: This has no effect on ShareExtension
- let bgTask = BGTaskHelper.startBackgroundTask(withName: "uploadAndShareFiles")
- // Hide keyboard before upload to correctly display the HUD
- self.textView.resignFirstResponder()
- NCIntentController.sharedInstance().donateSendMessageIntent(for: self.room)
- self.hud = MBProgressHUD.showAdded(to: self.view, animated: true)
- self.hud?.mode = .annularDeterminate
- self.hud?.label.text = String(format: NSLocalizedString("Uploading %ld elements", comment: ""), self.shareItemController.shareItems.count)
- if self.shareItemController.shareItems.count == 1 {
- self.hud?.label.text = NSLocalizedString("Uploading 1 element", comment: "")
- }
- self.uploadGroup = DispatchGroup()
- self.uploadErrors = []
- self.uploadSuccess = []
- // Add caption to last shareItem
- if let shareItem = self.shareItemController.shareItems.last {
- if NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityMediaCaption, forAccountId: self.account.accountId) {
- let messageParameters = NCMessageParameter.messageParametersJSONString(from: self.mentionsDict) ?? ""
- let message = NCChatMessage()
- message.message = self.replaceMentionsDisplayNamesWithMentionsKeysInMessage(message: self.textView.text, parameters: messageParameters)
- message.messageParametersJSONString = messageParameters
- shareItem.caption = message.sendingMessage
- }
- }
- for shareItem in self.shareItemController.shareItems {
- NSLog("Uploading \(shareItem.fileURL.absoluteString)")
- self.uploadGroup.enter()
- NCAPIController.sharedInstance().uniqueNameForFileUpload(withName: shareItem.fileName, originalName: true, for: self.account) { fileServerURL, fileServerPath, _, errorDescription in
- if let fileServerURL, let fileServerPath {
- self.uploadFile(to: fileServerURL, with: fileServerPath, with: shareItem)
- } else {
- NCUtils.log(String(format: "Error finding unique upload name. Error: %@", errorDescription ?? "Unknown error"))
- self.uploadErrors.append(errorDescription ?? "Unknown error")
- self.uploadGroup.leave()
- }
- }
- }
- self.uploadGroup.notify(queue: .main) {
- self.stopAnimatingSharingIndicator()
- self.hud?.hide(animated: true)
- // TODO: Do error reporting per item
- if self.uploadErrors.isEmpty {
- self.shareItemController.removeAllItems()
- self.delegate?.shareConfirmationViewControllerDidFinish(self)
- } else {
- // We remove the successfully uploaded items, so only the failed ones are kept
- self.shareItemController.remove(self.uploadSuccess)
- let alert = UIAlertController(title: NSLocalizedString("Upload failed", comment: ""),
- message: self.uploadErrors.joined(separator: "\n"),
- preferredStyle: .alert)
- alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default))
- self.present(alert, animated: true)
- }
- bgTask.stopBackgroundTask()
- }
- }
- func uploadFile(to fileServerURL: String, with filePath: String, with item: ShareItem) {
- NextcloudKit.shared.upload(serverUrlFileName: fileServerURL, fileNameLocalPath: item.filePath) { _ in
- NSLog("Upload task")
- } progressHandler: { progress in
- item.uploadProgress = progress.fractionCompleted
- self.updateHudProgress()
- } completionHandler: { _, _, _, _, _, _, nkError in
- if nkError.errorCode == 0 {
- var talkMetaData: [String: Any] = [:]
- let itemCaption = item.caption.trimmingCharacters(in: .whitespaces)
- if !itemCaption.isEmpty {
- talkMetaData["caption"] = itemCaption
- }
- if self.shareSilently {
- talkMetaData["silent"] = self.shareSilently
- }
- NCAPIController.sharedInstance().shareFileOrFolder(for: self.account, atPath: filePath, toRoom: self.room.token, talkMetaData: talkMetaData) { error in
- if let error {
- NCUtils.log(String(format: "Failed to share file. Error: %@", error.localizedDescription))
- self.uploadErrors.append(error.localizedDescription)
- } else {
- self.uploadSuccess.append(item)
- }
- self.uploadGroup.leave()
- }
- } else if nkError.errorCode == 404 || nkError.errorCode == 409 {
- NCAPIController.sharedInstance().checkOrCreateAttachmentFolder(for: self.account) { created, _ in
- if created {
- self.uploadFile(to: fileServerURL, with: filePath, with: item)
- } else {
- self.uploadErrors.append(nkError.errorDescription)
- self.uploadGroup.leave()
- }
- }
- } else {
- NCUtils.log(String(format: "Failed to upload file. Error: %@", nkError.errorDescription))
- self.uploadErrors.append(nkError.errorDescription)
- self.uploadGroup.leave()
- }
- }
- }
- // MARK: - User Interface
- func startAnimatingSharingIndicator() {
- DispatchQueue.main.async {
- self.sharingIndicatorView.startAnimating()
- self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: self.sharingIndicatorView)
- }
- }
- func stopAnimatingSharingIndicator() {
- DispatchQueue.main.async {
- self.sharingIndicatorView.stopAnimating()
- self.navigationItem.rightBarButtonItem = self.sendButton
- }
- }
- func updateToolbarForCurrentItem() {
- if let item = self.getCurrentShareItem() {
- UIView.transition(with: self.itemToolbar, duration: 0.3, options: .transitionCrossDissolve) {
- self.cropItemButton.isEnabled = item.isImage
- self.previewItemButton.isEnabled = QLPreviewController.canPreview(item.fileURL as QLPreviewItem)
- self.addItemButton.isEnabled = self.shareItemController.shareItems.count < 5
- }
- } else {
- self.cropItemButton.isEnabled = false
- self.previewItemButton.isEnabled = false
- }
- self.removeItemButton.isEnabled = self.shareItemController.shareItems.count > 1
- self.removeItemButton.tintColor = self.shareItemController.shareItems.count > 1 ? nil : .clear
- }
- // MARK: - UIImagePickerController Delegate
- public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
- self.saveImagePickerSettings(picker)
- guard let mediaType = info[.mediaType] as? String else { return }
- if mediaType == "public.image" {
- if let image = info[.originalImage] as? UIImage {
- self.dismiss(animated: true) {
- self.shareItemController.addItem(with: image)
- self.collectionViewScrollToEnd()
- }
- }
- } else if mediaType == "public.movie" {
- if let videoUrl = info[.mediaURL] as? URL {
- self.dismiss(animated: true) {
- self.shareItemController.addItem(with: videoUrl)
- self.collectionViewScrollToEnd()
- }
- }
- }
- }
- public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
- self.saveImagePickerSettings(picker)
- self.dismiss(animated: true)
- }
- func saveImagePickerSettings(_ picker: UIImagePickerController) {
- if picker.sourceType == .camera && picker.cameraCaptureMode == .photo {
- NCUserDefaults.setPreferredCameraFlashMode(picker.cameraFlashMode.rawValue)
- }
- }
- // MARK: - UIDocumentPickerViewController Delegate
- public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
- for documentURL in urls {
- self.shareItemController.addItem(with: documentURL)
- }
- self.collectionViewScrollToEnd()
- }
- // MARK: - ScrollView/CollectionView
- public override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
- guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kShareConfirmationCellIdentifier, for: indexPath) as? ShareConfirmationCollectionViewCell
- else { return UICollectionViewCell() }
- let item = self.shareItemController.shareItems[indexPath.row]
- // Setting placeholder here in case we can't generate any other preview
- cell.setPlaceHolderImage(item.placeholderImage)
- cell.setPlaceHolderText(item.fileName)
- if let fileURL = item.fileURL, NCUtils.isImage(fileExtension: fileURL.pathExtension),
- let image = self.shareItemController.getImageFrom(item) {
- // We're able to get an image directly from the fileURL -> use it
- cell.setPreviewImage(image)
- } else {
- self.generatePreview(for: cell, with: collectionView, with: item)
- }
- return cell
- }
- func generatePreview(for cell: ShareConfirmationCollectionViewCell, with collectionView: UICollectionView, with item: ShareItem) {
- let size = CGSize(width: collectionView.bounds.width, height: collectionView.bounds.height)
- let scale = self.view.window?.screen.scale ?? UIScreen.main.scale
- // updateHandler might be called multiple times, starting from low quality representation to high-quality
- let request = QLThumbnailGenerator.Request(fileAt: item.fileURL, size: size, scale: scale, representationTypes: [.lowQualityThumbnail, .thumbnail])
- QLThumbnailGenerator.shared.generateRepresentations(for: request) { thumbnail, _, error in
- guard error == nil, let thumbnail else { return }
- DispatchQueue.main.async {
- cell.setPreviewImage(thumbnail.uiImage)
- }
- }
- }
- public override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
- return self.shareItemController.shareItems.count
- }
- public override func numberOfSections(in collectionView: UICollectionView) -> Int {
- return 1
- }
- public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
- return CGSize(width: collectionView.bounds.width, height: collectionView.bounds.height)
- }
- public override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
- if self.textView.isFirstResponder {
- self.textView.resignFirstResponder()
- } else {
- self.previewCurrentItem()
- }
- }
- public override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
- self.updatePageControlPage()
- }
- public override func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
- self.updatePageControlPage()
- }
- func collectionViewScrollToEnd() {
- if let item = self.shareItemController.shareItems.last {
- self.scroll(to: item, animated: true)
- }
- }
- func scroll(to item: ShareItem, animated: Bool) {
- DispatchQueue.main.async {
- if let indexForItem = self.shareItemController.shareItems.firstIndex(of: item) {
- let indexPath = IndexPath(row: indexForItem, section: 0)
- self.shareCollectionView.scrollToItem(at: indexPath, at: [], animated: animated)
- }
- }
- }
- func getCurrentShareItem() -> ShareItem? {
- let currentIndex = Int(self.shareCollectionView.contentOffset.x / self.shareCollectionView.frame.size.width)
- if currentIndex >= self.shareItemController.shareItems.count {
- return nil
- }
- return self.shareItemController.shareItems[currentIndex]
- }
- // MARK: - PageControl
- func pageControlValueChanged() {
- let indexPath = IndexPath(row: self.pageControl.currentPage, section: 0)
- self.shareCollectionView.scrollToItem(at: indexPath, at: [], animated: true)
- }
- func updatePageControlPage() {
- // see: https://stackoverflow.com/a/46181277/2512312
- DispatchQueue.main.async {
- self.pageControl.currentPage = Int(self.shareCollectionView.contentOffset.x / self.shareCollectionView.frame.width)
- self.updateToolbarForCurrentItem()
- }
- }
- // MARK: - PreviewController
- func previewCurrentItem() {
- self.textView.resignFirstResponder()
- guard let item = self.getCurrentShareItem(),
- let fileURL = item.fileURL,
- QLPreviewController.canPreview(fileURL as QLPreviewItem)
- else { return }
- let preview = QLPreviewController()
- preview.dataSource = self
- preview.delegate = self
- preview.navigationController?.navigationBar.tintColor = NCAppBranding.themeTextColor()
- preview.navigationController?.navigationBar.barTintColor = NCAppBranding.themeColor()
- preview.tabBarController?.tabBar.tintColor = NCAppBranding.themeColor()
- let appearance = UINavigationBarAppearance()
- appearance.configureWithOpaqueBackground()
- appearance.titleTextAttributes = [.foregroundColor: NCAppBranding.themeTextColor()]
- appearance.backgroundColor = NCAppBranding.themeColor()
- self.navigationItem.standardAppearance = appearance
- self.navigationItem.compactAppearance = appearance
- self.navigationItem.scrollEdgeAppearance = appearance
- self.navigationController?.pushViewController(preview, animated: true)
- }
- public func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
- return 1
- }
- public func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
- // Don't use index here, as this relates to numberOfPreviewItems
- // When we have numberOfPreviewItems > 1 this will show an additional list of items
- guard let item = self.getCurrentShareItem(),
- let fileURL = item.fileURL
- else { return URL(fileURLWithPath: "") as QLPreviewItem }
- return fileURL as QLPreviewItem
- }
- public func previewController(_ controller: QLPreviewController, editingModeFor previewItem: QLPreviewItem) -> QLPreviewItemEditingMode {
- return .createCopy
- }
- public func previewController(_ controller: QLPreviewController, didSaveEditedCopyOf previewItem: QLPreviewItem, at modifiedContentsURL: URL) {
- if let item = self.getCurrentShareItem() {
- self.shareItemController.update(item, with: modifiedContentsURL)
- }
- }
- // MARK: - ShareItemController Delegate
- public func shareItemControllerItemsChanged(_ shareItemController: ShareItemController) {
- DispatchQueue.main.async {
- if shareItemController.shareItems.isEmpty {
- if let extensionContext = self.extensionContext {
- let error = NSError(domain: NSCocoaErrorDomain, code: 0)
- extensionContext.cancelRequest(withError: error)
- } else {
- self.dismiss(animated: true)
- }
- } else {
- self.shareCollectionView.reloadData()
- // Make sure all changes are fully populated before we update our UI elements
- self.shareCollectionView.layoutIfNeeded()
- self.updateToolbarForCurrentItem()
- self.pageControl.numberOfPages = shareItemController.shareItems.count
- }
- }
- }
- // MARK: - TOCropViewController Delegate
- public func cropViewController(_ cropViewController: TOCropViewController, didCropTo image: UIImage, with cropRect: CGRect, angle: Int) {
- if let item = self.getCurrentShareItem() {
- self.shareItemController.update(item, with: image)
- // Fixes bug on iPad where collectionView is scrolled between two pages
- self.scroll(to: item, animated: true)
- }
- // Fixes weird iOS 13 bug: https://github.com/TimOliver/TOCropViewController/issues/365
- cropViewController.transitioningDelegate = nil
- cropViewController.dismiss(animated: true)
- }
- public func cropViewController(_ cropViewController: TOCropViewController, didFinishCancelled cancelled: Bool) {
- if let item = self.getCurrentShareItem() {
- self.scroll(to: item, animated: true)
- }
- // Fixes weird iOS 13 bug: https://github.com/TimOliver/TOCropViewController/issues/365
- cropViewController.transitioningDelegate = nil
- cropViewController.dismiss(animated: true)
- }
- // MARK: - NKCommon Delegate
- public func authenticationChallenge(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
- // The pinning check
- if CCCertificate.sharedManager().checkTrustedChallenge(challenge) {
- completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
- } else {
- completionHandler(.performDefaultHandling, nil)
- }
- }
- }
|