// // PreviewAttachmentImageVideo.swift // Qmera // // Created by Akhmad Al Qindi Irsyam on 08/09/21. // import UIKit import AVKit import AVFoundation protocol PreviewAttachmentImageVideoDelegate : NSObjectProtocol { func sendChatFromPreviewImage(message_text: String, attachment_flag: String, image_id: String, video_id: String, thumb_id: String, viewController: UIViewController) } class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITextViewDelegate { @IBOutlet var imagePreview: UIImageView! @IBOutlet var buttonSend: UIButton! @IBOutlet var textFieldSend: UITextView! @IBOutlet var buttonCancel: UIButton! @IBOutlet var constraintViewTextField: NSLayoutConstraint! @IBOutlet var heightTextFieldSend: NSLayoutConstraint! @IBOutlet var constraintButtonSend: NSLayoutConstraint! @IBOutlet var scrollViewImage: UIScrollView! @IBOutlet weak var buttonAckConfidential: UIButton! @IBOutlet weak var constraintLeftTextField: NSLayoutConstraint! var imageVideoData: [UIImagePickerController.InfoKey: Any]? var image: UIImage? var currentTextTextField: String? var delegate: PreviewAttachmentImageVideoDelegate? var isHiddenTextField = false var fromCopy = false var isConfidential = false var isAck = false var isGroup = false var isCC = false override func viewDidLoad() { super.viewDidLoad() if (imageVideoData != nil) { if (imageVideoData![.mediaType] as! String == "public.movie") { do { let asset = AVURLAsset(url: imageVideoData![.mediaURL] as! URL, options: nil) let imgGenerator = AVAssetImageGenerator(asset: asset) imgGenerator.appliesPreferredTrackTransform = true let cgImage = try imgGenerator.copyCGImage(at: CMTimeMake(value: 0, timescale: 1), actualTime: nil) let thumbnail = UIImage(cgImage: cgImage) imagePreview.image = thumbnail let symbolPlay = UIImageView(image: UIImage(systemName: "play.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 50, weight: .bold, scale: .default))) imagePreview.addSubview(symbolPlay) symbolPlay.tintColor = .black.withAlphaComponent(0.5) symbolPlay.translatesAutoresizingMaskIntoConstraints = false symbolPlay.centerXAnchor.constraint(equalTo: imagePreview.centerXAnchor).isActive = true symbolPlay.centerYAnchor.constraint(equalTo: imagePreview.centerYAnchor).isActive = true let objectTap = ObjectGesture(target: self, action: #selector(previewImageVideoTapped(_:))) scrollViewImage.addGestureRecognizer(objectTap) objectTap.videoURL = imageVideoData![.mediaURL] as? NSURL } catch let error { //print("*** Error generating thumbnail: \(error.localizedDescription)") } } else { imagePreview.image = imageVideoData![.originalImage] as? UIImage } } else { imagePreview.image = image } if ((imageVideoData != nil && imageVideoData![.mediaType] as! String == "public.image") || isHiddenTextField) { scrollViewImage.maximumZoomScale = 4 scrollViewImage.minimumZoomScale = 1 scrollViewImage.delegate = self } if (isHiddenTextField) { textFieldSend.removeFromSuperview() buttonSend.removeFromSuperview() buttonAckConfidential.removeFromSuperview() } else { buttonSend.setImage(resizeImage(image: UIImage(named: "Send-(White)", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal), for: .normal) buttonSend.circle() buttonSend.addTarget(self, action: #selector(sendTapped), for: .touchUpInside) if isCC { buttonAckConfidential.isHidden = true constraintLeftTextField.constant = 20 } else { buttonAckConfidential.circle() buttonAckConfidential.addTarget(self, action: #selector(showChooserACKConfidential), for: .touchUpInside) let imageConfidential = resizeImage(image: UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal) let imageAck = resizeImage(image: UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal) if isAck { buttonAckConfidential.setImage(imageAck, for: .normal) } else if isConfidential { buttonAckConfidential.setImage(imageConfidential, for: .normal) } } textFieldSend.layer.cornerRadius = textFieldSend.maxCornerRadius() textFieldSend.layer.borderWidth = 1.0 if (currentTextTextField == "" || currentTextTextField == nil) { textFieldSend.text = "Send message".localized() textFieldSend.textColor = UIColor.lightGray } else { textFieldSend.text = currentTextTextField } textFieldSend.textContainerInset = UIEdgeInsets(top: 12, left: 20, bottom: 12, right: 40) textFieldSend.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.5).cgColor textFieldSend.font = UIFont.systemFont(ofSize: 12) textFieldSend.delegate = self textFieldSend.allowsEditingTextAttributes = true let center: NotificationCenter = NotificationCenter.default center.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) center.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil) let dismissKeyboard = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) view.addGestureRecognizer(dismissKeyboard) } buttonCancel.circle() buttonCancel.backgroundColor = .secondaryColor.withAlphaComponent(0.4) buttonCancel.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside) } @objc func showChooserACKConfidential() { let alertController = LibAlertController(title: "Message Mode".localized(), message: "Select".localized() + " " + "Message Mode".localized(), preferredStyle: .actionSheet) if !self.isGroup { let imageConfidential = resizeImage(image: UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal) let confidentialAction = UIAlertAction(title: "Confidential Message".localized(), style: .default, handler: { (UIAlertAction) in if !self.isConfidential { self.isConfidential = true self.buttonAckConfidential.setImage(imageConfidential, for: .normal) } if self.isAck { self.isAck = false } self.setPreviousVariableMessageMode() }) confidentialAction.setValue(imageConfidential, forKey: "image") alertController.addAction(confidentialAction) } let imageAck = resizeImage(image: UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal) let ackAction = UIAlertAction(title: "Confirmation Message".localized(), style: .default, handler: { (UIAlertAction) in if !self.isAck { self.isAck = true self.buttonAckConfidential.setImage(imageAck, for: .normal) } if !self.isGroup { if self.isConfidential { self.isConfidential = false } } self.setPreviousVariableMessageMode() }) ackAction.setValue(imageAck, forKey: "image") alertController.addAction(ackAction) alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: { (UIAlertAction) in if !self.isGroup { self.isConfidential = false } self.isAck = false self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal) self.setPreviousVariableMessageMode() })) self.present(alertController, animated: true, completion: nil) } func setPreviousVariableMessageMode() { let stack = self.presentingViewController as! UINavigationController let vc = stack.viewControllers[stack.viewControllers.count - 1] if vc is EditorPersonal { let editorVc = vc as! EditorPersonal editorVc.setAckConfidential(isAck: self.isAck, isConfidential: self.isConfidential) } else { let editorVc = vc as! EditorGroup editorVc.setAckConfidential(isAck: self.isAck, isConfidential: self.isConfidential) } } func textViewDidChange(_ textView: UITextView) { textView.attributedText = textView.text.richText(isEditing: true) } func textViewDidBeginEditing(_ textView: UITextView) { if textView.textColor == UIColor.lightGray { textView.text = nil textView.textColor = UIColor.black } } func textViewDidEndEditing(_ textView: UITextView) { if textView.text.isEmpty { textView.text = "Send message".localized() textView.textColor = UIColor.lightGray } } func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { let cursorPosition = textView.caretRect(for: self.textFieldSend.selectedTextRange!.start).origin let currentLine = Int(cursorPosition.y / self.textFieldSend.font!.lineHeight) UIView.animate(withDuration: 0.3) { if currentLine == 0 { self.heightTextFieldSend.constant = 40 } else if currentLine < 4 { self.heightTextFieldSend.constant = self.textFieldSend.contentSize.height// Padding } } return true } @objc func previewImageVideoTapped(_ sender: ObjectGesture) { let player = AVPlayer(url: sender.videoURL! as URL) let playerVC = AVPlayerViewController() playerVC.player = player playerVC.modalPresentationStyle = .custom self.present(playerVC, animated: true, completion: nil) } func viewForZooming(in scrollView: UIScrollView) -> UIView? { return imagePreview } func scrollViewDidZoom(_ scrollView: UIScrollView) { if scrollViewImage.zoomScale > 1 { if let image = imagePreview.image { let ratioW = imagePreview.frame.width / image.size.width let ratioH = imagePreview.frame.height / image.size.height let ratio = ratioW < ratioH ? ratioW : ratioH let newWidth = image.size.width * ratio let newHeight = image.size.height * ratio let conditionLeft = newWidth*scrollViewImage.zoomScale > imagePreview.frame.width let left = 0.5 * (conditionLeft ? newWidth - imagePreview.frame.width : (scrollViewImage.frame.width - scrollViewImage.contentSize.width)) let conditionTop = newHeight*scrollViewImage.zoomScale > imagePreview.frame.height let top = 0.01 * (conditionTop ? newHeight - imagePreview.frame.width : (scrollViewImage.frame.height - scrollViewImage.contentSize.height)) scrollViewImage.contentInset = UIEdgeInsets(top: top, left: left, bottom: top, right: left) } } else { scrollViewImage.contentInset = .zero } } @objc func dismissKeyboard() { textFieldSend.resignFirstResponder() // dismiss keyoard } @objc func keyboardWillShow(notification: NSNotification) { let info:NSDictionary = notification.userInfo! as NSDictionary let keyboardSize = (info[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue let keyboardHeight: CGFloat = keyboardSize.height let _: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseInOut, animations: { self.constraintViewTextField.constant = keyboardHeight + 10 self.constraintButtonSend.constant = keyboardHeight + 10 }, completion: nil) } @objc func keyboardWillHide(notification: NSNotification) { UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: { self.constraintViewTextField.constant = 20 self.constraintButtonSend.constant = 20 }, completion: nil) } @objc func sendTapped() { if fromCopy || (imageVideoData![.mediaType] as! String == "public.image") { var originalImageName = "" if (fromCopy) { originalImageName = "\(Date().currentTimeMillis())_copyImage" } else if (imageVideoData![.imageURL] == nil) { originalImageName = "\(Date().currentTimeMillis())_takeImage" } else { let urlImage = (imageVideoData![.imageURL] as! NSURL).absoluteString originalImageName = (urlImage! as NSString).lastPathComponent } let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let compressedImageName = "THUMB-Qmera_image_\(originalImageName)" let thumbName = "THUMB_Qmera_image_\(originalImageName)" let fileURL = documentsDirectory.appendingPathComponent(compressedImageName) var compressedImage:Data? if (fromCopy) { compressedImage = image!.jpegData(compressionQuality: 1.0) } else { compressedImage = (imageVideoData![.originalImage] as! UIImage).jpegData(compressionQuality: 1.0) } if let data = compressedImage, !FileManager.default.fileExists(atPath: fileURL.path) { do { try data.write(to: fileURL) //print("file saved") } catch { //print("error saving file:", error) } } let thumbImage = UIImage(data: compressedImage!) let fileURLTHUMB = documentsDirectory.appendingPathComponent(thumbName) if let dataThumb = thumbImage!.jpegData(compressionQuality: 0.25), !FileManager.default.fileExists(atPath: fileURLTHUMB.path) { do { try dataThumb.write(to: fileURLTHUMB) //print("thumb saved") } catch { //print("error saving file:", error) } } self.dismiss(animated: true, completion: nil) if (textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) == "Send message".localized() && textFieldSend.textColor == UIColor.lightGray) { delegate!.sendChatFromPreviewImage(message_text: "", attachment_flag: "1", image_id: compressedImageName, video_id: "", thumb_id: thumbName, viewController: self) } else { delegate!.sendChatFromPreviewImage(message_text: textFieldSend.text!, attachment_flag: "1", image_id: compressedImageName, video_id: "", thumb_id: thumbName, viewController: self) } } else { guard var dataVideo = try? Data(contentsOf: imageVideoData![.mediaURL] as! URL) else { return } let sizeOfVideo = Double(dataVideo.count / 1048576) if (sizeOfVideo > 10.0) { let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + UUID().uuidString + ".mp4") compressVideo(inputURL: imageVideoData![.mediaURL] as! URL, outputURL: compressedURL) { exportSession in guard let session = exportSession else { return } switch session.status { case .unknown: break case .waiting: break case .exporting: break case .completed: guard let compressedData = try? Data(contentsOf: compressedURL) else { return } dataVideo = compressedData case .failed: break case .cancelled: break @unknown default: break } } } let urlVideo = (imageVideoData![.mediaURL] as! NSURL).absoluteString let originalVideoName = (urlVideo! as NSString).lastPathComponent let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let renamedVideoName = "Qmera_video_\(originalVideoName)" let thumbName = "THUMB_Qmera_video_\(originalVideoName)" let fileURL = documentsDirectory.appendingPathComponent(renamedVideoName) if !FileManager.default.fileExists(atPath: fileURL.path) { do { try dataVideo.write(to: fileURL) //print("file saved") } catch { //print("error saving file:", error) } } let dataThumbVideo = imagePreview.image!.jpegData(compressionQuality: 1.0) let fileURLTHUMB = documentsDirectory.appendingPathComponent(thumbName) if !FileManager.default.fileExists(atPath: fileURLTHUMB.path) { do { try dataThumbVideo!.write(to: fileURLTHUMB) //print("thumb saved") } catch { //print("error saving file:", error) } } self.dismiss(animated: true, completion: nil) if (textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) == "Send message".localized() && textFieldSend.textColor == UIColor.lightGray) { delegate!.sendChatFromPreviewImage(message_text: "", attachment_flag: "2", image_id: "", video_id: renamedVideoName, thumb_id: thumbName, viewController: self) } else { delegate!.sendChatFromPreviewImage(message_text: textFieldSend.text!, attachment_flag: "2", image_id: "", video_id: renamedVideoName, thumb_id: thumbName, viewController: self) } } } func compressVideo(inputURL: URL, outputURL: URL, handler:@escaping (_ exportSession: AVAssetExportSession?) -> Void) { let urlAsset = AVURLAsset(url: inputURL, options: nil) guard let exportSession = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetMediumQuality) else { handler(nil) return } exportSession.outputURL = outputURL exportSession.outputFileType = .mp4 exportSession.exportAsynchronously { handler(exportSession) } } @objc func cancelTapped() { self.dismiss(animated: true, completion: nil) } }