Browse Source

thumbnails are generated on background thread + cached and restored from cache

nayooti 5 năm trước cách đây
mục cha
commit
258d046528

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

@@ -145,6 +145,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 */; };
@@ -429,6 +431,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>"; };
@@ -598,7 +602,11 @@
 				3059620D234614E700C80F33 /* DcContact+Extension.swift */,
 				3059620F2346154D00C80F33 /* String+Extension.swift */,
 				302B84CD2397F6CD001C261F /* URL+Extension.swift */,
+<<<<<<< HEAD
 				AE0AA957247834A400D42A7F /* Date+Extension.swift */,
+=======
+				AE6EC5232497663200A400E4 /* UIImageView+Extensions.swift */,
+>>>>>>> thumbnails are generated on background thread + cached and restored from cache
 			);
 			path = Extensions;
 			sourceTree = "<group>";
@@ -939,7 +947,11 @@
 				AE1988A423EB2FBA00B4CD5F /* Errors.swift */,
 				AEFBE23023FF09B20045327A /* TypeAlias.swift */,
 				307D822D241669C7006D2490 /* LocationManager.swift */,
+<<<<<<< HEAD
 				AE0AA9552478191900D42A7F /* GridCollectionViewFlowLayout.swift */,
+=======
+				AE6EC5272497B9B200A400E4 /* ThumbnailCache.swift */,
+>>>>>>> thumbnails are generated on background thread + cached and restored from cache
 			);
 			path = Helper;
 			sourceTree = "<group>";
@@ -1359,6 +1371,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 */,
@@ -1438,6 +1451,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 - 4
deltachat-ios/DC/DcMsg+Extension.swift

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

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

@@ -0,0 +1,17 @@
+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
+                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))
+    }
+}

+ 8 - 1
deltachat-ios/MessageKit/Layout/MediaMessageSizeCalculator.swift

@@ -31,6 +31,8 @@ open class MediaMessageSizeCalculator: MessageSizeCalculator {
         return UIScreen.main.bounds.size.height * 0.7
     }
 
+    private let defaultHeight: CGFloat = 250
+
     open override func messageContainerSize(for message: MessageType) -> CGSize {
         let maxWidth = messageContainerMaxWidth(for: message)
         let sizeForMediaItem = { (maxWidth: CGFloat, item: MediaItem) -> CGSize in
@@ -54,7 +56,12 @@ open class MediaMessageSizeCalculator: MessageSizeCalculator {
         case .photo(let item):
             return sizeForMediaItem(maxWidth, item)
         case .video(let item):
-            return sizeForMediaItem(maxWidth, item)
+            if item.image == nil {
+                // no cached thumbnail -> is generated asynchronously
+                return CGSize(width: maxWidth, height: defaultHeight)
+            } else {
+                return sizeForMediaItem(maxWidth, item)
+            }
         default:
             fatalError("messageContainerSize received unhandled MessageDataType: \(message.kind)")
         }

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

@@ -40,6 +40,10 @@ open class MediaMessageCell: MessageContentCell {
         return imageView
     }()
 
+    private lazy var imageViewHeightConstraint: NSLayoutConstraint = {
+        return imageView.heightAnchor.constraint(equalToConstant: 350)
+    }()
+
     // MARK: - Methods
 
     /// Responsible for setting up the constraints of the cell's subviews.
@@ -70,10 +74,25 @@ open class MediaMessageCell: MessageContentCell {
 
         switch message.kind {
         case .photo(let mediaItem):
+            imageViewHeightConstraint.isActive = false
             imageView.image = mediaItem.image ?? mediaItem.placeholderImage
             playButtonView.isHidden = true
         case .video(let mediaItem):
-            imageView.image = mediaItem.image ?? mediaItem.placeholderImage
+            if let url = mediaItem.url {
+                imageViewHeightConstraint.isActive = true
+                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 +100,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)
+    }
 }