PreviewAttachmentImageVideo.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. //
  2. // PreviewAttachmentImageVideo.swift
  3. // Qmera
  4. //
  5. // Created by Akhmad Al Qindi Irsyam on 08/09/21.
  6. //
  7. import UIKit
  8. import AVKit
  9. import AVFoundation
  10. protocol PreviewAttachmentImageVideoDelegate : NSObjectProtocol {
  11. func sendChatFromPreviewImage(message_text: String, attachment_flag: String, image_id: String, video_id: String, thumb_id: String, viewController: UIViewController)
  12. }
  13. class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITextViewDelegate {
  14. @IBOutlet var imagePreview: UIImageView!
  15. @IBOutlet var buttonSend: UIButton!
  16. @IBOutlet var textFieldSend: UITextView!
  17. @IBOutlet var buttonCancel: UIButton!
  18. @IBOutlet var constraintViewTextField: NSLayoutConstraint!
  19. @IBOutlet var heightTextFieldSend: NSLayoutConstraint!
  20. @IBOutlet var constraintButtonSend: NSLayoutConstraint!
  21. @IBOutlet var scrollViewImage: UIScrollView!
  22. @IBOutlet weak var buttonAckConfidential: UIButton!
  23. @IBOutlet weak var constraintLeftTextField: NSLayoutConstraint!
  24. var imageVideoData: [UIImagePickerController.InfoKey: Any]?
  25. var image: UIImage?
  26. var currentTextTextField: String?
  27. var delegate: PreviewAttachmentImageVideoDelegate?
  28. var isHiddenTextField = false
  29. var fromCopy = false
  30. var isConfidential = false
  31. var isAck = false
  32. var isGroup = false
  33. var isCC = false
  34. override func viewDidLoad() {
  35. super.viewDidLoad()
  36. if (imageVideoData != nil) {
  37. if (imageVideoData![.mediaType] as! String == "public.movie") {
  38. do {
  39. let asset = AVURLAsset(url: imageVideoData![.mediaURL] as! URL, options: nil)
  40. let imgGenerator = AVAssetImageGenerator(asset: asset)
  41. imgGenerator.appliesPreferredTrackTransform = true
  42. let cgImage = try imgGenerator.copyCGImage(at: CMTimeMake(value: 0, timescale: 1), actualTime: nil)
  43. let thumbnail = UIImage(cgImage: cgImage)
  44. imagePreview.image = thumbnail
  45. let symbolPlay = UIImageView(image: UIImage(systemName: "play.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 50, weight: .bold, scale: .default)))
  46. imagePreview.addSubview(symbolPlay)
  47. symbolPlay.tintColor = .black.withAlphaComponent(0.5)
  48. symbolPlay.translatesAutoresizingMaskIntoConstraints = false
  49. symbolPlay.centerXAnchor.constraint(equalTo: imagePreview.centerXAnchor).isActive = true
  50. symbolPlay.centerYAnchor.constraint(equalTo: imagePreview.centerYAnchor).isActive = true
  51. let objectTap = ObjectGesture(target: self, action: #selector(previewImageVideoTapped(_:)))
  52. scrollViewImage.addGestureRecognizer(objectTap)
  53. objectTap.videoURL = imageVideoData![.mediaURL] as? NSURL
  54. } catch let error {
  55. print("*** Error generating thumbnail: \(error.localizedDescription)")
  56. }
  57. } else {
  58. imagePreview.image = imageVideoData![.originalImage] as? UIImage
  59. }
  60. } else {
  61. imagePreview.image = image
  62. }
  63. if ((imageVideoData != nil && imageVideoData![.mediaType] as! String == "public.image") || isHiddenTextField) {
  64. scrollViewImage.maximumZoomScale = 4
  65. scrollViewImage.minimumZoomScale = 1
  66. scrollViewImage.delegate = self
  67. }
  68. if (isHiddenTextField) {
  69. textFieldSend.removeFromSuperview()
  70. buttonSend.removeFromSuperview()
  71. buttonAckConfidential.removeFromSuperview()
  72. } else {
  73. 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)
  74. buttonSend.circle()
  75. buttonSend.addTarget(self, action: #selector(sendTapped), for: .touchUpInside)
  76. if isCC {
  77. buttonAckConfidential.isHidden = true
  78. constraintLeftTextField.constant = 20
  79. } else {
  80. buttonAckConfidential.circle()
  81. buttonAckConfidential.addTarget(self, action: #selector(showChooserACKConfidential), for: .touchUpInside)
  82. let imageConfidential = resizeImage(image: UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  83. let imageAck = resizeImage(image: UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  84. if isAck {
  85. buttonAckConfidential.setImage(imageAck, for: .normal)
  86. } else if isConfidential {
  87. buttonAckConfidential.setImage(imageConfidential, for: .normal)
  88. }
  89. }
  90. textFieldSend.layer.cornerRadius = textFieldSend.maxCornerRadius()
  91. textFieldSend.layer.borderWidth = 1.0
  92. if (currentTextTextField == "" || currentTextTextField == nil) {
  93. textFieldSend.text = "Send message".localized()
  94. textFieldSend.textColor = UIColor.lightGray
  95. } else {
  96. textFieldSend.text = currentTextTextField
  97. }
  98. textFieldSend.textContainerInset = UIEdgeInsets(top: 12, left: 20, bottom: 12, right: 40)
  99. textFieldSend.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.5).cgColor
  100. textFieldSend.font = UIFont.systemFont(ofSize: 12)
  101. textFieldSend.delegate = self
  102. textFieldSend.allowsEditingTextAttributes = true
  103. let center: NotificationCenter = NotificationCenter.default
  104. center.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
  105. center.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
  106. let dismissKeyboard = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
  107. view.addGestureRecognizer(dismissKeyboard)
  108. }
  109. buttonCancel.circle()
  110. buttonCancel.backgroundColor = .secondaryColor.withAlphaComponent(0.4)
  111. buttonCancel.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
  112. }
  113. @objc func showChooserACKConfidential() {
  114. let alertController = UIAlertController(title: "Message Mode".localized(), message: "Select".localized() + " " + "Message Mode".localized(), preferredStyle: .actionSheet)
  115. if !self.isGroup {
  116. let imageConfidential = resizeImage(image: UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  117. let confidentialAction = UIAlertAction(title: "Confidential Message".localized(), style: .default, handler: { (UIAlertAction) in
  118. if !self.isConfidential {
  119. self.isConfidential = true
  120. self.buttonAckConfidential.setImage(imageConfidential, for: .normal)
  121. }
  122. if self.isAck {
  123. self.isAck = false
  124. }
  125. self.setPreviousVariableMessageMode()
  126. })
  127. confidentialAction.setValue(imageConfidential, forKey: "image")
  128. alertController.addAction(confidentialAction)
  129. }
  130. let imageAck = resizeImage(image: UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  131. let ackAction = UIAlertAction(title: "Confirmation Message".localized(), style: .default, handler: { (UIAlertAction) in
  132. if !self.isAck {
  133. self.isAck = true
  134. self.buttonAckConfidential.setImage(imageAck, for: .normal)
  135. }
  136. if !self.isGroup {
  137. if self.isConfidential {
  138. self.isConfidential = false
  139. }
  140. }
  141. self.setPreviousVariableMessageMode()
  142. })
  143. ackAction.setValue(imageAck, forKey: "image")
  144. alertController.addAction(ackAction)
  145. alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: { (UIAlertAction) in
  146. if !self.isGroup {
  147. self.isConfidential = false
  148. }
  149. self.isAck = false
  150. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  151. self.setPreviousVariableMessageMode()
  152. }))
  153. self.present(alertController, animated: true, completion: nil)
  154. }
  155. func setPreviousVariableMessageMode() {
  156. let stack = self.presentingViewController as! UINavigationController
  157. let vc = stack.viewControllers[stack.viewControllers.count - 1]
  158. if vc is EditorPersonal {
  159. let editorVc = vc as! EditorPersonal
  160. editorVc.setAckConfidential(isAck: self.isAck, isConfidential: self.isConfidential)
  161. } else {
  162. let editorVc = vc as! EditorGroup
  163. editorVc.setAckConfidential(isAck: self.isAck, isConfidential: self.isConfidential)
  164. }
  165. }
  166. func textViewDidChange(_ textView: UITextView) {
  167. textView.attributedText = textView.text.richText(isEditing: true)
  168. }
  169. func textViewDidBeginEditing(_ textView: UITextView) {
  170. if textView.textColor == UIColor.lightGray {
  171. textView.text = nil
  172. textView.textColor = UIColor.black
  173. }
  174. }
  175. func textViewDidEndEditing(_ textView: UITextView) {
  176. if textView.text.isEmpty {
  177. textView.text = "Send message".localized()
  178. textView.textColor = UIColor.lightGray
  179. }
  180. }
  181. func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
  182. let cursorPosition = textView.caretRect(for: self.textFieldSend.selectedTextRange!.start).origin
  183. let currentLine = Int(cursorPosition.y / self.textFieldSend.font!.lineHeight)
  184. UIView.animate(withDuration: 0.3) {
  185. if currentLine == 0 {
  186. self.heightTextFieldSend.constant = 40
  187. } else if currentLine < 4 {
  188. self.heightTextFieldSend.constant = self.textFieldSend.contentSize.height// Padding
  189. }
  190. }
  191. return true
  192. }
  193. @objc func previewImageVideoTapped(_ sender: ObjectGesture) {
  194. let player = AVPlayer(url: sender.videoURL! as URL)
  195. let playerVC = AVPlayerViewController()
  196. playerVC.player = player
  197. playerVC.modalPresentationStyle = .custom
  198. self.present(playerVC, animated: true, completion: nil)
  199. }
  200. func viewForZooming(in scrollView: UIScrollView) -> UIView? {
  201. return imagePreview
  202. }
  203. func scrollViewDidZoom(_ scrollView: UIScrollView) {
  204. if scrollViewImage.zoomScale > 1 {
  205. if let image = imagePreview.image {
  206. let ratioW = imagePreview.frame.width / image.size.width
  207. let ratioH = imagePreview.frame.height / image.size.height
  208. let ratio = ratioW < ratioH ? ratioW : ratioH
  209. let newWidth = image.size.width * ratio
  210. let newHeight = image.size.height * ratio
  211. let conditionLeft = newWidth*scrollViewImage.zoomScale > imagePreview.frame.width
  212. let left = 0.5 * (conditionLeft ? newWidth - imagePreview.frame.width : (scrollViewImage.frame.width - scrollViewImage.contentSize.width))
  213. let conditionTop = newHeight*scrollViewImage.zoomScale > imagePreview.frame.height
  214. let top = 0.01 * (conditionTop ? newHeight - imagePreview.frame.width : (scrollViewImage.frame.height - scrollViewImage.contentSize.height))
  215. scrollViewImage.contentInset = UIEdgeInsets(top: top, left: left, bottom: top, right: left)
  216. }
  217. } else {
  218. scrollViewImage.contentInset = .zero
  219. }
  220. }
  221. @objc func dismissKeyboard() {
  222. textFieldSend.resignFirstResponder() // dismiss keyoard
  223. }
  224. @objc func keyboardWillShow(notification: NSNotification) {
  225. let info:NSDictionary = notification.userInfo! as NSDictionary
  226. let keyboardSize = (info[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
  227. let keyboardHeight: CGFloat = keyboardSize.height
  228. let _: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
  229. UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseInOut, animations: {
  230. self.constraintViewTextField.constant = keyboardHeight + 10
  231. self.constraintButtonSend.constant = keyboardHeight + 10
  232. }, completion: nil)
  233. }
  234. @objc func keyboardWillHide(notification: NSNotification) {
  235. UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
  236. self.constraintViewTextField.constant = 20
  237. self.constraintButtonSend.constant = 20
  238. }, completion: nil)
  239. }
  240. @objc func sendTapped() {
  241. if fromCopy || (imageVideoData![.mediaType] as! String == "public.image") {
  242. var originalImageName = ""
  243. if (fromCopy) {
  244. originalImageName = "\(Date().currentTimeMillis())_copyImage"
  245. } else if (imageVideoData![.imageURL] == nil) {
  246. originalImageName = "\(Date().currentTimeMillis())_takeImage"
  247. } else {
  248. let urlImage = (imageVideoData![.imageURL] as! NSURL).absoluteString
  249. originalImageName = (urlImage! as NSString).lastPathComponent
  250. }
  251. let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
  252. let compressedImageName = "THUMB-Qmera_image_\(originalImageName)"
  253. let thumbName = "THUMB_Qmera_image_\(originalImageName)"
  254. let fileURL = documentsDirectory.appendingPathComponent(compressedImageName)
  255. var compressedImage:Data?
  256. if (fromCopy) {
  257. compressedImage = image!.jpegData(compressionQuality: 1.0)
  258. } else {
  259. compressedImage = (imageVideoData![.originalImage] as! UIImage).jpegData(compressionQuality: 1.0)
  260. }
  261. if let data = compressedImage,
  262. !FileManager.default.fileExists(atPath: fileURL.path) {
  263. do {
  264. try data.write(to: fileURL)
  265. print("file saved")
  266. } catch {
  267. print("error saving file:", error)
  268. }
  269. }
  270. let thumbImage = UIImage(data: compressedImage!)
  271. let fileURLTHUMB = documentsDirectory.appendingPathComponent(thumbName)
  272. if let dataThumb = thumbImage!.jpegData(compressionQuality: 0.25),
  273. !FileManager.default.fileExists(atPath: fileURLTHUMB.path) {
  274. do {
  275. try dataThumb.write(to: fileURLTHUMB)
  276. print("thumb saved")
  277. } catch {
  278. print("error saving file:", error)
  279. }
  280. }
  281. self.dismiss(animated: true, completion: nil)
  282. if (textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) == "Send message".localized() && textFieldSend.textColor == UIColor.lightGray) {
  283. delegate!.sendChatFromPreviewImage(message_text: "", attachment_flag: "1", image_id: compressedImageName, video_id: "", thumb_id: thumbName, viewController: self)
  284. } else {
  285. delegate!.sendChatFromPreviewImage(message_text: textFieldSend.text!, attachment_flag: "1", image_id: compressedImageName, video_id: "", thumb_id: thumbName, viewController: self)
  286. }
  287. } else {
  288. guard var dataVideo = try? Data(contentsOf: imageVideoData![.mediaURL] as! URL) else {
  289. return
  290. }
  291. let sizeOfVideo = Double(dataVideo.count / 1048576)
  292. if (sizeOfVideo > 10.0) {
  293. let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + UUID().uuidString + ".mp4")
  294. compressVideo(inputURL: imageVideoData![.mediaURL] as! URL,
  295. outputURL: compressedURL) { exportSession in
  296. guard let session = exportSession else {
  297. return
  298. }
  299. switch session.status {
  300. case .unknown:
  301. break
  302. case .waiting:
  303. break
  304. case .exporting:
  305. break
  306. case .completed:
  307. guard let compressedData = try? Data(contentsOf: compressedURL) else {
  308. return
  309. }
  310. dataVideo = compressedData
  311. case .failed:
  312. break
  313. case .cancelled:
  314. break
  315. @unknown default:
  316. break
  317. }
  318. }
  319. }
  320. let urlVideo = (imageVideoData![.mediaURL] as! NSURL).absoluteString
  321. let originalVideoName = (urlVideo! as NSString).lastPathComponent
  322. let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
  323. let renamedVideoName = "Qmera_video_\(originalVideoName)"
  324. let thumbName = "THUMB_Qmera_video_\(originalVideoName)"
  325. let fileURL = documentsDirectory.appendingPathComponent(renamedVideoName)
  326. if !FileManager.default.fileExists(atPath: fileURL.path) {
  327. do {
  328. try dataVideo.write(to: fileURL)
  329. print("file saved")
  330. } catch {
  331. print("error saving file:", error)
  332. }
  333. }
  334. let dataThumbVideo = imagePreview.image!.jpegData(compressionQuality: 1.0)
  335. let fileURLTHUMB = documentsDirectory.appendingPathComponent(thumbName)
  336. if !FileManager.default.fileExists(atPath: fileURLTHUMB.path) {
  337. do {
  338. try dataThumbVideo!.write(to: fileURLTHUMB)
  339. print("thumb saved")
  340. } catch {
  341. print("error saving file:", error)
  342. }
  343. }
  344. self.dismiss(animated: true, completion: nil)
  345. if (textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) == "Send message".localized() && textFieldSend.textColor == UIColor.lightGray) {
  346. delegate!.sendChatFromPreviewImage(message_text: "", attachment_flag: "2", image_id: "", video_id: renamedVideoName, thumb_id: thumbName, viewController: self)
  347. } else {
  348. delegate!.sendChatFromPreviewImage(message_text: textFieldSend.text!, attachment_flag: "2", image_id: "", video_id: renamedVideoName, thumb_id: thumbName, viewController: self)
  349. }
  350. }
  351. }
  352. func compressVideo(inputURL: URL,
  353. outputURL: URL,
  354. handler:@escaping (_ exportSession: AVAssetExportSession?) -> Void) {
  355. let urlAsset = AVURLAsset(url: inputURL, options: nil)
  356. guard let exportSession = AVAssetExportSession(asset: urlAsset,
  357. presetName: AVAssetExportPresetMediumQuality) else {
  358. handler(nil)
  359. return
  360. }
  361. exportSession.outputURL = outputURL
  362. exportSession.outputFileType = .mp4
  363. exportSession.exportAsynchronously {
  364. handler(exportSession)
  365. }
  366. }
  367. @objc func cancelTapped() {
  368. self.dismiss(animated: true, completion: nil)
  369. }
  370. }