Эх сурвалжийг харах

rearrange files and implement custom image preview

cyberta 5 жил өмнө
parent
commit
8cab62aa69

+ 3 - 3
DcCore/DcCore/Extensions/UIImage+Extensions.swift

@@ -37,11 +37,11 @@ public extension UIImage {
         return newImage
     }
 
-    public func dcCompress(toMax target: Float = 1280) -> UIImage? {
+    func dcCompress(toMax target: Float = 1280) -> UIImage? {
         return scaleDownAndCompress(toMax: target)
     }
 
-    public func imageSizeInPixel() -> CGSize {
+    func imageSizeInPixel() -> CGSize {
         let heightInPoints = size.height
         let heightInPixels = heightInPoints * scale
         let widthInPoints = size.width
@@ -52,7 +52,7 @@ public extension UIImage {
     // if an image has an alpha channel we try to keep it, using PNG formatting instead of JPEG
     // PNGs are less compressed than JPEGs - to keep the message sizes small,
     // the size of PNG imgaes will be scaled down
-    public func scaleDownAndCompress(toMax: Float) -> UIImage? {
+    func scaleDownAndCompress(toMax: Float) -> UIImage? {
         let rect = getResizedRectangle(toMax: self.isTransparent() ?
             min(Float(self.size.width) / 2, toMax / 2) :
             toMax)

+ 17 - 0
DcCore/DcCore/Helper/DcUtils.swift

@@ -1,6 +1,7 @@
 import Foundation
 import UIKit
 import MobileCoreServices
+import AVFoundation
 
 public struct DcUtils {
 
@@ -94,4 +95,20 @@ public struct DcUtils {
         return "application/octet-stream"
     }
 
+    public static func generateThumbnailFromVideo(url: URL?) -> UIImage? {
+           guard let url = url else {
+               return nil
+           }
+           do {
+               let asset = AVURLAsset(url: url)
+               let imageGenerator = AVAssetImageGenerator(asset: asset)
+               imageGenerator.appliesPreferredTrackTransform = true
+               let cgImage = try imageGenerator.copyCGImage(at: .zero, actualTime: nil)
+               return UIImage(cgImage: cgImage)
+           } catch {
+               print(error.localizedDescription)
+               return nil
+           }
+       }
+
 }

+ 0 - 0
DcShare/ChatListController.swift → DcShare/Controller/ChatListController.swift


+ 0 - 0
DcShare/SendingController.swift → DcShare/Controller/SendingController.swift


+ 21 - 1
DcShare/ShareViewController.swift → DcShare/Controller/ShareViewController.swift

@@ -35,6 +35,14 @@ class ShareViewController: SLComposeServiceViewController {
     let dbHelper = DatabaseHelper()
     var shareAttachment: ShareAttachment?
 
+    lazy var preview: UIImageView? = {
+        let imageView = UIImageView(frame: .zero)
+        imageView.clipsToBounds = true
+        imageView.shouldGroupAccessibilityChildren = true
+        imageView.isAccessibilityElement = false
+        return imageView
+    }()
+
     override func viewDidLoad() {
         super.viewDidLoad()
         setupNavigationBar()
@@ -66,6 +74,10 @@ class ShareViewController: SLComposeServiceViewController {
         }
     }
 
+    override func loadPreviewView() -> UIView! {
+        return preview
+    }
+
     override func isContentValid() -> Bool {
         // Do validation of contentText and/or NSExtensionContext attachments here
         return  !(contentText?.isEmpty ?? true) || !(self.shareAttachment?.isEmpty ?? true)
@@ -151,9 +163,17 @@ extension ShareViewController: SendingControllerDelegate {
 }
 
 extension ShareViewController: ShareAttachmentDelegate {
-    func onAttachmentAdded() {
+    func onAttachmentChanged() {
         DispatchQueue.main.async {
             self.validateContent()
         }
     }
+
+    func onThumbnailChanged() {
+        DispatchQueue.main.async {
+            if let preview = self.preview {
+                preview.image = self.shareAttachment?.thumbnail ?? nil
+            }
+        }
+    }
 }

+ 173 - 0
DcShare/Helper/ShareAttachment.swift

@@ -0,0 +1,173 @@
+import Foundation
+import MobileCoreServices
+import DcCore
+import UIKit
+import QuickLookThumbnailing
+
+protocol ShareAttachmentDelegate: class {
+    func onAttachmentChanged()
+    func onThumbnailChanged()
+}
+
+class ShareAttachment {
+
+    weak var delegate: ShareAttachmentDelegate?
+    let dcContext: DcContext
+    let thumbnailSize = CGFloat(96)
+
+    var inputItems: [Any]?
+    var messages: [DcMsg] = []
+
+    private var imageThumbnail: UIImage?
+    private var attachmentThumbnail: UIImage?
+
+    var thumbnail: UIImage? {
+        return self.imageThumbnail ?? self.attachmentThumbnail
+    }
+
+    var isEmpty: Bool {
+        return messages.isEmpty
+    }
+
+    init(dcContext: DcContext, inputItems: [Any]?, delegate: ShareAttachmentDelegate) {
+        self.dcContext = dcContext
+        self.inputItems = inputItems
+        self.delegate = delegate
+        createMessages()
+    }
+
+
+    func createMessages() {
+        guard let items = inputItems as? [NSExtensionItem] else { return }
+        for item in items {
+            if let attachments = item.attachments {
+                createMessageFromDataRepresentaion(attachments)
+            }
+        }
+    }
+
+    // a NSExtensionItem can have multiple attachments representing the same data in diffent types
+    // we want only one DcMsg per NSExtensionItem which is why we're breaking out of the loop
+    // after the first match
+    func createMessageFromDataRepresentaion(_ attachments: [NSItemProvider]) {
+        for attachment in attachments {
+            if attachment.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
+                createImageMsg(attachment)
+                break
+            } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeMovie as String) {
+                createMovieMsg(attachment)
+                break
+            } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeAudio as String) {
+                createAudioMsg(attachment)
+                break
+            } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeFileURL as String) {
+                createFileMsg(attachment)
+                break
+            }
+        }
+    }
+
+    func createImageMsg(_ item: NSItemProvider) {
+        item.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil) { data, error in
+            let result: UIImage?
+            switch data {
+            case let image as UIImage:
+                result = image
+            case let data as Data:
+                result = UIImage(data: data)
+            case let url as URL:
+                result = UIImage(contentsOfFile: url.path)
+            default:
+                self.dcContext.logger?.debug("Unexpected data: \(type(of: data))")
+                result = nil
+            }
+            if let result = result, let compressedImage = result.dcCompress() {
+                let pixelSize = compressedImage.imageSizeInPixel()
+                let path = DcUtils.saveImage(image: compressedImage)
+                let msg = DcMsg(viewType: DC_MSG_IMAGE)
+                msg.setFile(filepath: path, mimeType: "image/jpeg")
+                msg.setDimension(width: pixelSize.width, height: pixelSize.height)
+                self.delegate?.onAttachmentChanged()
+                self.messages.append(msg)
+                if self.imageThumbnail == nil {
+                    self.imageThumbnail = compressedImage.scaleDownImage(toMax: self.thumbnailSize)
+                    self.delegate?.onThumbnailChanged()
+                }
+            }
+        }
+    }
+
+    func createMovieMsg(_ item: NSItemProvider) {
+        item.loadItem(forTypeIdentifier: kUTTypeMovie as String, options: nil) { data, error in
+            switch data {
+            case let url as URL:
+                self.addDcMsg(url: url, viewType: DC_MSG_VIDEO)
+                self.delegate?.onAttachmentChanged()
+                if self.imageThumbnail == nil {
+                    self.imageThumbnail = DcUtils.generateThumbnailFromVideo(url: url)?.scaleDownImage(toMax: self.thumbnailSize)
+                    self.delegate?.onThumbnailChanged()
+                }
+
+            default:
+                self.dcContext.logger?.debug("Unexpected data: \(type(of: data))")
+            }
+        }
+    }
+
+    func createAudioMsg(_ item: NSItemProvider) {
+        createMessageFromItemURL(item: item, typeIdentifier: kUTTypeAudio, viewType: DC_MSG_AUDIO)
+    }
+
+    func createFileMsg(_ item: NSItemProvider) {
+        createMessageFromItemURL(item: item, typeIdentifier: kUTTypeFileURL, viewType: DC_MSG_FILE)
+    }
+
+    func createMessageFromItemURL(item: NSItemProvider, typeIdentifier: CFString, viewType: Int32) {
+        item.loadItem(forTypeIdentifier: typeIdentifier as String, options: nil) { data, error in
+            switch data {
+            case let url as URL:
+                self.addDcMsg(url: url, viewType: viewType)
+                self.delegate?.onAttachmentChanged()
+                if self.imageThumbnail == nil {
+                    self.generateThumbnailRepresentations(url: url)
+                }
+            default:
+                self.dcContext.logger?.debug("Unexpected data: \(type(of: data))")
+            }
+        }
+    }
+
+    func addDcMsg(url: URL, viewType: Int32) {
+        let msg = DcMsg(viewType: DC_MSG_VIDEO)
+        msg.setFile(filepath: url.path, mimeType: DcUtils.getMimeTypeForPath(path: url.path))
+        self.messages.append(msg)
+    }
+
+    func generateThumbnailRepresentations(url: URL) {
+        let size: CGSize = CGSize(width: self.thumbnailSize * 2 / 3, height: self.thumbnailSize)
+        let scale = UIScreen.main.scale
+
+        if #available(iOSApplicationExtension 13.0, *) {
+            let request = QLThumbnailGenerator.Request(fileAt: url,
+                                                       size: size,
+                                                       scale: scale,
+                                                       representationTypes: .all)
+            let generator = QLThumbnailGenerator.shared
+            generator.generateRepresentations(for: request) { (thumbnail, type, error) in
+                DispatchQueue.main.async {
+                    if thumbnail == nil || error != nil {
+                        self.dcContext.logger?.warning(error?.localizedDescription ?? "Could not create thumbnail.")
+                    } else {
+                        self.attachmentThumbnail = thumbnail?.uiImage
+                        self.delegate?.onThumbnailChanged()
+                    }
+                }
+            }
+        } else {
+            let controller = UIDocumentInteractionController(url: url)
+            self.attachmentThumbnail = controller.icons.first
+            self.delegate?.onThumbnailChanged()
+        }
+    }
+
+}

+ 0 - 110
DcShare/ShareAttachment.swift

@@ -1,110 +0,0 @@
-import Foundation
-import MobileCoreServices
-import DcCore
-import UIKit
-
-protocol ShareAttachmentDelegate: class {
-    func onAttachmentAdded()
-}
-class ShareAttachment {
-
-    weak var delegate: ShareAttachmentDelegate?
-    let dcContext: DcContext
-
-    var inputItems: [Any]?
-    var messages: [DcMsg] = []
-
-    var isEmpty: Bool {
-        return messages.isEmpty
-    }
-
-    init(dcContext: DcContext, inputItems: [Any]?, delegate: ShareAttachmentDelegate) {
-        self.dcContext = dcContext
-        self.inputItems = inputItems
-        self.delegate = delegate
-        createMessages()
-    }
-
-
-    func createMessages() {
-        guard let items = inputItems as? [NSExtensionItem] else { return }
-        for item in items {
-            if let attachments = item.attachments {
-                createMessageFromDataRepresentaion(attachments)
-            }
-        }
-    }
-
-    // a NSExtensionItem can have multiple attachments representing the same data in diffent types
-    // we want only one DcMsg per NSExtensionItem which is why we're breaking out of the loop
-    // after the first match
-    func createMessageFromDataRepresentaion(_ attachments: [NSItemProvider]) {
-        for attachment in attachments {
-            if attachment.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
-                createImageMsg(attachment)
-                break
-            } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeMovie as String) {
-                createMoviewMsg(attachment)
-                break
-            } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeAudio as String) {
-                createAudioMsg(attachment)
-                break
-            } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeFileURL as String) {
-                createFileMsg(attachment)
-                break
-            }
-        }
-    }
-
-    func createImageMsg(_ item: NSItemProvider) {
-        item.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil) { data, error in
-            let result: UIImage?
-            switch data {
-            case let image as UIImage:
-                result = image
-            case let data as Data:
-                result = UIImage(data: data)
-            case let url as URL:
-                result = UIImage(contentsOfFile: url.path)
-            default:
-                self.dcContext.logger?.debug("Unexpected data: \(type(of: data))")
-                result = nil
-            }
-            if let result = result, let compressedImage = result.dcCompress() {
-                let pixelSize = compressedImage.imageSizeInPixel()
-                let path = DcUtils.saveImage(image: compressedImage)
-                let msg = DcMsg(viewType: DC_MSG_IMAGE)
-                msg.setFile(filepath: path, mimeType: "image/jpeg")
-                msg.setDimension(width: pixelSize.width, height: pixelSize.height)
-                self.messages.append(msg)
-                self.delegate?.onAttachmentAdded()
-            }
-        }
-    }
-
-    func createMoviewMsg(_ item: NSItemProvider) {
-         createDcMsgFromURL(item: item, typeIdentifier: kUTTypeMovie, viewType: DC_MSG_VIDEO)
-    }
-
-    func createAudioMsg(_ item: NSItemProvider) {
-        createDcMsgFromURL(item: item, typeIdentifier: kUTTypeAudio, viewType: DC_MSG_AUDIO)
-    }
-
-    func createFileMsg(_ item: NSItemProvider) {
-        createDcMsgFromURL(item: item, typeIdentifier: kUTTypeFileURL, viewType: DC_MSG_FILE)
-    }
-
-    func createDcMsgFromURL(item: NSItemProvider, typeIdentifier: CFString, viewType: Int32) {
-        item.loadItem(forTypeIdentifier: typeIdentifier as String, options: nil) { data, error in
-            switch data {
-            case let url as URL:
-                let msg = DcMsg(viewType: viewType)
-                msg.setFile(filepath: url.path, mimeType: DcUtils.getMimeTypeForPath(path: url.path))
-                self.messages.append(msg)
-                self.delegate?.onAttachmentAdded()
-            default:
-                self.dcContext.logger?.debug("Unexpected data: \(type(of: data))")
-            }
-        }
-    }
-}

+ 0 - 0
DcShare/ChatListCell.swift → DcShare/View/ChatListCell.swift


+ 8 - 8
deltachat-ios.xcodeproj/project.pbxproj

@@ -534,16 +534,20 @@
 			path = DC;
 			sourceTree = "<group>";
 		};
-		304F5E452451D27500462538 /* Extensions */ = {
+		304F5E452451D27500462538 /* Helper */ = {
 			isa = PBXGroup;
 			children = (
+				302589FE2452FA280086C1CD /* ShareAttachment.swift */,
 			);
-			path = Extensions;
+			path = Helper;
 			sourceTree = "<group>";
 		};
 		304F5E462451D2AA00462538 /* Controller */ = {
 			isa = PBXGroup;
 			children = (
+				30E8F2122447285600CE2C90 /* ShareViewController.swift */,
+				30E8F2412448B77600CE2C90 /* ChatListController.swift */,
+				30E8F252244DAD0E00CE2C90 /* SendingController.swift */,
 			);
 			path = Controller;
 			sourceTree = "<group>";
@@ -551,6 +555,7 @@
 		304F5E472451D2CA00462538 /* View */ = {
 			isa = PBXGroup;
 			children = (
+				30E8F2432449C64100CE2C90 /* ChatListCell.swift */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -712,15 +717,10 @@
 			children = (
 				304F5E472451D2CA00462538 /* View */,
 				304F5E462451D2AA00462538 /* Controller */,
-				304F5E452451D27500462538 /* Extensions */,
+				304F5E452451D27500462538 /* Helper */,
 				30E8F21F24472AAE00CE2C90 /* DcShare.entitlements */,
-				30E8F2122447285600CE2C90 /* ShareViewController.swift */,
-				30E8F2412448B77600CE2C90 /* ChatListController.swift */,
 				30E8F2142447285600CE2C90 /* MainInterface.storyboard */,
 				30E8F2172447285600CE2C90 /* Info.plist */,
-				30E8F2432449C64100CE2C90 /* ChatListCell.swift */,
-				30E8F252244DAD0E00CE2C90 /* SendingController.swift */,
-				302589FE2452FA280086C1CD /* ShareAttachment.swift */,
 			);
 			path = DcShare;
 			sourceTree = "<group>";

+ 1 - 1
deltachat-ios/DC/DcMsg+Extension.swift

@@ -46,7 +46,7 @@ extension DcMsg: MessageType {
     }
 
     internal func createVideoMessage(text: String) -> MessageKind {
-        let thumbnail = Utils.generateThumbnailFromVideo(url: fileURL)
+        let thumbnail = DcUtils.generateThumbnailFromVideo(url: fileURL)
         if text.isEmpty {
             return MessageKind.video(Media(url: fileURL, image: thumbnail))
         }

+ 1 - 16
deltachat-ios/Helper/Utils.swift

@@ -1,6 +1,5 @@
 import Foundation
 import UIKit
-import AVFoundation
 import DcCore
 
 struct Utils {
@@ -58,21 +57,7 @@ struct Utils {
         return url.absoluteString.hasSuffix("wav")
     }
 
-    static func generateThumbnailFromVideo(url: URL?) -> UIImage? {
-        guard let url = url else {
-            return nil
-        }
-        do {
-            let asset = AVURLAsset(url: url)
-            let imageGenerator = AVAssetImageGenerator(asset: asset)
-            imageGenerator.appliesPreferredTrackTransform = true
-            let cgImage = try imageGenerator.copyCGImage(at: .zero, actualTime: nil)
-            return UIImage(cgImage: cgImage)
-        } catch {
-            print(error.localizedDescription)
-            return nil
-        }
-    }
+   
 
     static func getDeviceLanguage() -> String? {
         // some device languages have suffixes (like en-aus etc.) so we want to cut suffixes off