浏览代码

Merge pull request #787 from deltachat/async_thumbnails

Async thumbnail loading in chatView
cyBerta 5 年之前
父节点
当前提交
e7c083da59

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

@@ -146,6 +146,8 @@
 		AE4AEE3522B1030D000AA495 /* PreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE4AEE3422B1030D000AA495 /* PreviewController.swift */; };
 		AE52EA19229EB53C00C586C9 /* ContactDetailHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE52EA18229EB53C00C586C9 /* ContactDetailHeader.swift */; };
 		AE52EA20229EB9F000C586C9 /* EditGroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE52EA1F229EB9F000C586C9 /* EditGroupViewController.swift */; };
+		AE6EC5242497663200A400E4 /* UIImageView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE6EC5232497663200A400E4 /* UIImageView+Extensions.swift */; };
+		AE6EC5282497B9B200A400E4 /* ThumbnailCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE6EC5272497B9B200A400E4 /* ThumbnailCache.swift */; };
 		AE728F15229D5C390047565B /* PhotoPickerAlertAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE728F14229D5C390047565B /* PhotoPickerAlertAction.swift */; };
 		AE76E5EE242BF2EA003CF461 /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE76E5ED242BF2EA003CF461 /* WelcomeViewController.swift */; };
 		AE77838D23E32ED20093EABD /* ContactDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE77838C23E32ED20093EABD /* ContactDetailViewModel.swift */; };
@@ -431,6 +433,8 @@
 		AE4AEE3422B1030D000AA495 /* PreviewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewController.swift; sourceTree = "<group>"; };
 		AE52EA18229EB53C00C586C9 /* ContactDetailHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailHeader.swift; sourceTree = "<group>"; };
 		AE52EA1F229EB9F000C586C9 /* EditGroupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditGroupViewController.swift; sourceTree = "<group>"; };
+		AE6EC5232497663200A400E4 /* UIImageView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImageView+Extensions.swift"; sourceTree = "<group>"; };
+		AE6EC5272497B9B200A400E4 /* ThumbnailCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbnailCache.swift; sourceTree = "<group>"; };
 		AE728F14229D5C390047565B /* PhotoPickerAlertAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPickerAlertAction.swift; sourceTree = "<group>"; };
 		AE76E5ED242BF2EA003CF461 /* WelcomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = "<group>"; };
 		AE77838C23E32ED20093EABD /* ContactDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailViewModel.swift; sourceTree = "<group>"; };
@@ -601,6 +605,7 @@
 				3059620F2346154D00C80F33 /* String+Extension.swift */,
 				302B84CD2397F6CD001C261F /* URL+Extension.swift */,
 				AE0AA957247834A400D42A7F /* Date+Extension.swift */,
+				AE6EC5232497663200A400E4 /* UIImageView+Extensions.swift */,
 			);
 			path = Extensions;
 			sourceTree = "<group>";
@@ -943,6 +948,7 @@
 				AEFBE23023FF09B20045327A /* TypeAlias.swift */,
 				307D822D241669C7006D2490 /* LocationManager.swift */,
 				AE0AA9552478191900D42A7F /* GridCollectionViewFlowLayout.swift */,
+				AE6EC5272497B9B200A400E4 /* ThumbnailCache.swift */,
 			);
 			path = Helper;
 			sourceTree = "<group>";
@@ -1363,6 +1369,7 @@
 				3040F460234F419400FA34D5 /* BasicAudioController.swift in Sources */,
 				30A2EC36247D72720024ADD8 /* AnimatedImageMessageCell.swift in Sources */,
 				305962082346125100C80F33 /* MediaMessageSizeCalculator.swift in Sources */,
+				AE6EC5282497B9B200A400E4 /* ThumbnailCache.swift in Sources */,
 				AE52EA20229EB9F000C586C9 /* EditGroupViewController.swift in Sources */,
 				AE18F294228C602A0007B1BE /* SecuritySettingsController.swift in Sources */,
 				305961FD2346125100C80F33 /* TypingBubble.swift in Sources */,
@@ -1442,6 +1449,7 @@
 				305961DD2346125100C80F33 /* SenderType.swift in Sources */,
 				305961E32346125100C80F33 /* MessagesDataSource.swift in Sources */,
 				AEFBE22F23FEF23D0045327A /* ProviderInfoCell.swift in Sources */,
+				AE6EC5242497663200A400E4 /* UIImageView+Extensions.swift in Sources */,
 				305961E22346125100C80F33 /* MessagesDisplayDelegate.swift in Sources */,
 				305962092346125100C80F33 /* AudioMessageSizeCalculator.swift in Sources */,
 				305961DB2346125100C80F33 /* AudioItem.swift in Sources */,

+ 9 - 3
deltachat-ios/DC/DcMsg+Extension.swift

@@ -48,12 +48,18 @@ extension DcMsg: MessageType {
     }
 
     internal func createVideoMessage(text: String) -> MessageKind {
-        let thumbnail = DcUtils.generateThumbnailFromVideo(url: fileURL)
+        var thumbnail: UIImage?
+        if let fileURL = fileURL {
+            thumbnail = ThumbnailCache.shared.restoreImage(key: fileURL.absoluteString)
+        }
         if text.isEmpty {
+
             return MessageKind.video(Media(url: fileURL, image: thumbnail))
         }
-        let attributedString = NSAttributedString(string: text, attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .body),
-                                                                             NSAttributedString.Key.foregroundColor: DcColors.defaultTextColor])
+        let attributedString = NSAttributedString(string: text, attributes: [
+            NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .body),
+            NSAttributedString.Key.foregroundColor: DcColors.defaultTextColor]
+        )
         return MessageKind.videoText(Media(url: fileURL, image: thumbnail, text: [attributedString]))
     }
 

+ 18 - 0
deltachat-ios/Extensions/UIImageView+Extensions.swift

@@ -0,0 +1,18 @@
+import UIKit
+import DcCore
+
+extension UIImageView {
+
+    func loadVideoThumbnail(from url: URL, placeholderImage: UIImage?, completionHandler: ((UIImage?) -> Void)?) {
+
+        self.image = placeholderImage
+        DispatchQueue.global(qos: .background).async {
+            let thumbnailImage = DcUtils.generateThumbnailFromVideo(url: url)
+            DispatchQueue.main.async { [weak self] in
+                self?.image = thumbnailImage
+                self?.setNeedsDisplay()
+                completionHandler?(thumbnailImage)
+            }
+        }
+    }
+}

+ 21 - 0
deltachat-ios/Helper/ThumbnailCache.swift

@@ -0,0 +1,21 @@
+import UIKit
+
+class ThumbnailCache {
+
+    static let shared = ThumbnailCache()
+    private init() { }
+
+    private lazy var cache: NSCache<NSString, UIImage> = {
+        let cache = NSCache<NSString, UIImage>()
+        cache.name = "thumbnail_cache"
+        return cache
+    }()
+
+    func storeImage(image: UIImage, key: String){
+        cache.setObject(image, forKey: NSString(string: key))
+    }
+
+    func restoreImage(key: String) -> UIImage? {
+        return cache.object(forKey: NSString(string: key))
+    }
+}

+ 2 - 2
deltachat-ios/MessageKit/Layout/MediaMessageSizeCalculator.swift

@@ -53,8 +53,8 @@ open class MediaMessageSizeCalculator: MessageSizeCalculator {
         switch message.kind {
         case .photo(let item):
             return sizeForMediaItem(maxWidth, item)
-        case .video(let item):
-            return sizeForMediaItem(maxWidth, item)
+        case .video:
+            return CGSize(width: maxWidth, height: maxWidth)
         default:
             fatalError("messageContainerSize received unhandled MessageDataType: \(message.kind)")
         }

+ 10 - 1
deltachat-ios/MessageKit/Layout/TextMediaMessageSizeCalculator.swift

@@ -81,13 +81,22 @@ open class TextMediaMessageSizeCalculator: MessageSizeCalculator {
 
             var messageContainerSize = CGSize(width: imageWidth, height: imageHeight)
             switch message.kind {
-            case .photoText(let mediaItem), .videoText(let mediaItem), .animatedImageText(let mediaItem):
+            case .photoText(let mediaItem), .animatedImageText(let mediaItem):
                 if let text = mediaItem.text?[MediaItemConstants.messageText] {
                     let textHeight = text.height(withConstrainedWidth: maxTextWidth)
                     messageContainerSize.height += textHeight
                     messageContainerSize.height +=  self.messageLabelInsets(for: message).vertical
                 }
                 return messageContainerSize
+            case .videoText(let mediaItem):
+                var videoContainerSize = CGSize(width: self.minTextWidth, height: self.minTextWidth)
+                if let text = mediaItem.text?[MediaItemConstants.messageText] {
+                    let textHeight = text.height(withConstrainedWidth: maxTextWidth)
+                    // static size for thumbnails
+                    videoContainerSize.height += textHeight
+                    videoContainerSize.height += self.messageLabelInsets(for: message).vertical
+                }
+                return videoContainerSize
             default:
                 return messageContainerSize
             }

+ 19 - 1
deltachat-ios/MessageKit/Views/Cells/MediaMessageCell.swift

@@ -37,6 +37,7 @@ open class MediaMessageCell: MessageContentCell {
     open var imageView: UIImageView = {
         let imageView = UIImageView()
         imageView.contentMode = .scaleAspectFill
+        imageView.clipsToBounds = true
         return imageView
     }()
 
@@ -73,7 +74,20 @@ open class MediaMessageCell: MessageContentCell {
             imageView.image = mediaItem.image ?? mediaItem.placeholderImage
             playButtonView.isHidden = true
         case .video(let mediaItem):
-            imageView.image = mediaItem.image ?? mediaItem.placeholderImage
+            if let url = mediaItem.url {
+                if let image = mediaItem.image {
+                    imageView.image = image
+                } else {
+                    // no image in cache
+                    imageView.loadVideoThumbnail(from: url, placeholderImage: mediaItem.placeholderImage, completionHandler: { [weak self] thumbnail in
+                        if let image = thumbnail {
+                            self?.cache(thumbnail: image, key: url.absoluteString)
+                        }
+                    })
+                }
+            } else {
+                imageView.image = mediaItem.placeholderImage
+            }
             playButtonView.isHidden = false
         default:
             break
@@ -81,4 +95,8 @@ open class MediaMessageCell: MessageContentCell {
 
         displayDelegate.configureMediaMessageImageView(imageView, for: message, at: indexPath, in: messagesCollectionView)
     }
+
+    private func cache(thumbnail image: UIImage, key: String) {
+        ThumbnailCache.shared.storeImage(image: image, key: key)
+    }
 }

+ 28 - 2
deltachat-ios/MessageKit/Views/Cells/TextMediaMessageCell.swift

@@ -25,6 +25,7 @@ open class TextMediaMessageCell: MessageContentCell {
         let imageView = UIImageView()
         imageView.contentMode = .scaleAspectFill
         imageView.translatesAutoresizingMaskIntoConstraints = false
+        imageView.clipsToBounds = true
         return imageView
     }()
 
@@ -106,14 +107,36 @@ open class TextMediaMessageCell: MessageContentCell {
         }
 
         switch message.kind {
-        case .photoText(let mediaItem), .videoText(let mediaItem), .fileText(let mediaItem):
+        case .photoText(let mediaItem), .fileText(let mediaItem):
             configureImageView(for: mediaItem)
             configureMessageLabel(for: mediaItem,
                                   with: displayDelegate,
                                   message: message,
                                   at: indexPath,
                                   in: messagesCollectionView)
-
+        case .videoText(let mediaItem):
+            configureMessageLabel(
+                for: mediaItem,
+                with: displayDelegate,
+                message: message,
+                at: indexPath,
+                in: messagesCollectionView
+            )
+            if let url = mediaItem.url {
+                if let image = mediaItem.image {
+                    imageView.image = image
+                } else {
+                    // no image in cache
+                    imageView.loadVideoThumbnail(from: url, placeholderImage: mediaItem.placeholderImage, completionHandler: { [weak self] thumbnail in
+                            if let image = thumbnail {
+                                self?.cache(thumbnail: image, key: url.absoluteString)
+                            }
+                        }
+                    )
+                }
+            } else {
+                imageView.image = mediaItem.placeholderImage
+            }
         default:
             fatalError("Unexpected message kind in TextMediaMessageCell")
         }
@@ -124,6 +147,9 @@ open class TextMediaMessageCell: MessageContentCell {
         displayDelegate.configureMediaMessageImageView(imageView, for: message, at: indexPath, in: messagesCollectionView)
     }
 
+    private func cache(thumbnail image: UIImage, key: String) {
+        ThumbnailCache.shared.storeImage(image: image, key: key)
+    }
 
     private func configurePlayButtonView(for messageKind: MessageKind) {
         switch messageKind {