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

add video message handling in chat view

cyberta 4 жил өмнө
parent
commit
b0c0c8df0f

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

@@ -20,6 +20,7 @@
 		302B84C6239676F0001C261F /* AvatarHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30AC265E237F1807002A943F /* AvatarHelper.swift */; };
 		302B84C72396770B001C261F /* RelayHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B84C42396627F001C261F /* RelayHelper.swift */; };
 		302B84CE2397F6CD001C261F /* URL+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B84CD2397F6CD001C261F /* URL+Extension.swift */; };
+		302E1BB4252B5AB4008F4264 /* NewPlayButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302E1BB3252B5AB4008F4264 /* NewPlayButtonView.swift */; };
 		3040F45E234DFBC000FA34D5 /* Audio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3040F45D234DFBC000FA34D5 /* Audio.swift */; };
 		3040F460234F419400FA34D5 /* BasicAudioController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3040F45F234F419300FA34D5 /* BasicAudioController.swift */; };
 		3040F462234F550300FA34D5 /* AudioPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3040F461234F550300FA34D5 /* AudioPlayerView.swift */; };
@@ -280,6 +281,7 @@
 		30260CA6238F02F700D8D52C /* MultilineTextFieldCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineTextFieldCell.swift; sourceTree = "<group>"; };
 		302B84C42396627F001C261F /* RelayHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayHelper.swift; sourceTree = "<group>"; };
 		302B84CD2397F6CD001C261F /* URL+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extension.swift"; sourceTree = "<group>"; };
+		302E1BB3252B5AB4008F4264 /* NewPlayButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NewPlayButtonView.swift; path = "deltachat-ios/Chat/Views/NewPlayButtonView.swift"; sourceTree = SOURCE_ROOT; };
 		3040F45D234DFBC000FA34D5 /* Audio.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Audio.swift; sourceTree = "<group>"; };
 		3040F45F234F419300FA34D5 /* BasicAudioController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicAudioController.swift; sourceTree = "<group>"; };
 		3040F461234F550300FA34D5 /* AudioPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerView.swift; sourceTree = "<group>"; };
@@ -804,6 +806,7 @@
 			children = (
 				30FDB6B624D193DD0066C48D /* Cells */,
 				30E348DE24F3F819005C93D1 /* ChatTableView.swift */,
+				302E1BB3252B5AB4008F4264 /* NewPlayButtonView.swift */,
 				30F8817524DA97DA0023780E /* BackgroundContainer.swift */,
 				3008CB7324F9436C00E6A617 /* NewAudioPlayerView.swift */,
 			);
@@ -1473,6 +1476,7 @@
 				305961D32346125100C80F33 /* MessagesViewController+Keyboard.swift in Sources */,
 				3008CB7224F93EB900E6A617 /* NewAudioMessageCell.swift in Sources */,
 				305961EF2346125100C80F33 /* HorizontalEdgeInsets.swift in Sources */,
+				302E1BB4252B5AB4008F4264 /* NewPlayButtonView.swift in Sources */,
 				305961D62346125100C80F33 /* MessageInputBar.swift in Sources */,
 				305961ED2346125100C80F33 /* DetectorType.swift in Sources */,
 				305962062346125100C80F33 /* TypingIndicatorCellSizeCalculator.swift in Sources */,

+ 1 - 1
deltachat-ios/Chat/ChatViewControllerNew.swift

@@ -337,7 +337,7 @@ class ChatViewControllerNew: UITableViewController {
         }
 
         let cell: BaseMessageCell
-        if message.type == DC_MSG_IMAGE || message.type == DC_MSG_GIF {
+        if message.type == DC_MSG_IMAGE || message.type == DC_MSG_GIF || message.type == DC_MSG_VIDEO {
             cell = tableView.dequeueReusableCell(withIdentifier: "image", for: indexPath) as? NewImageTextCell ?? NewImageTextCell()
         } else if message.type == DC_MSG_FILE {
             cell = tableView.dequeueReusableCell(withIdentifier: "file", for: indexPath) as? NewFileTextCell ?? NewFileTextCell()

+ 83 - 24
deltachat-ios/Chat/Views/Cells/NewImageTextCell.swift

@@ -22,14 +22,26 @@ class NewImageTextCell: BaseMessageCell {
     lazy var contentImageView: SDAnimatedImageView = {
         let imageView = SDAnimatedImageView()
         imageView.translatesAutoresizingMaskIntoConstraints = false
-        imageView.contentMode = .scaleAspectFit
         imageView.setContentHuggingPriority(.defaultHigh, for: .vertical)
         imageView.isUserInteractionEnabled = true
+        imageView.contentMode = .scaleAspectFill
+        imageView.clipsToBounds = true
         return imageView
     }()
 
+    /// The play button view to display on video messages.
+    open lazy var playButtonView: PlayButtonView = {
+        let playButtonView = PlayButtonView()
+        playButtonView.isHidden = true
+        translatesAutoresizingMaskIntoConstraints = false
+        return playButtonView
+    }()
+
     override func setupSubviews() {
         super.setupSubviews()
+        contentImageView.addSubview(playButtonView)
+        playButtonView.centerInSuperview()
+        playButtonView.constraint(equalTo: CGSize(width: 50, height: 50))
         mainContentView.addArrangedSubview(contentImageView)
         mainContentView.addArrangedSubview(messageLabel)
         contentImageView.constraintAlignLeadingMaxTo(mainContentView).isActive = true
@@ -41,8 +53,11 @@ class NewImageTextCell: BaseMessageCell {
 
     override func update(msg: DcMsg, messageStyle: UIRectCorner, isAvatarVisible: Bool) {
         messageLabel.text = msg.text
+        tag = msg.id
         if msg.type == DC_MSG_IMAGE, let image = msg.image {
             contentImageView.image = image
+            playButtonView.isHidden = true
+            setAspectRatioFor(message: msg)
         } else if msg.type == DC_MSG_GIF, let url = msg.fileURL {
             contentImageView.sd_setImage(with: url,
                                          placeholderImage: UIImage(color: UIColor.init(alpha: 0,
@@ -50,8 +65,35 @@ class NewImageTextCell: BaseMessageCell {
                                                                                        green: 255,
                                                                                        blue: 255),
                                                                    size: CGSize(width: 500, height: 500)))
+            playButtonView.isHidden = true
+            setAspectRatioFor(message: msg)
+        } else if msg.type == DC_MSG_VIDEO, let url = msg.fileURL {
+            playButtonView.isHidden = false
+            if let image = ThumbnailCache.shared.restoreImage(key: url.absoluteString) {
+                contentImageView.image = image
+                setAspectRatioFor(message: msg, with: image, isPlaceholder: false)
+            } else {
+                // no image in cache
+                let placeholderImage = UIImage(color: UIColor.init(alpha: 0,
+                                                                   red: 255,
+                                                                   green: 255,
+                                                                   blue: 255),
+                                               size: CGSize(width: 250, height: 250))
+                contentImageView.image = placeholderImage
+                DispatchQueue.global(qos: .userInteractive).async {
+                    let thumbnailImage = DcUtils.generateThumbnailFromVideo(url: url)
+                    if let thumbnailImage = thumbnailImage {
+                        DispatchQueue.main.async { [weak self] in
+                            if msg.id == self?.tag {
+                                self?.contentImageView.image = thumbnailImage
+                                ThumbnailCache.shared.storeImage(image: thumbnailImage, key: url.absoluteString)
+                            }
+                        }
+                    }
+                }
+                setAspectRatioFor(message: msg, with: placeholderImage, isPlaceholder: true)
+            }
         }
-        setAspectRatioFor(msg: msg)
         super.update(msg: msg, messageStyle: messageStyle, isAvatarVisible: isAvatarVisible)
     }
 
@@ -61,34 +103,51 @@ class NewImageTextCell: BaseMessageCell {
         }
     }
 
-    private func setAspectRatioFor(msg: DcMsg) {
-        guard let image = msg.image else {
-           return
-       }
-
-       self.imageHeightConstraint?.isActive = false
-       self.imageWidthConstraint?.isActive = false
-       var messageWidth = msg.messageWidth
-       var messageHeight = msg.messageHeight
-       if messageWidth == 0 || messageHeight == 0 {
-           messageWidth = image.size.width
-           messageHeight = image.size.height
-           msg.setLateFilingMediaSize(width: messageWidth, height: messageHeight, duration: 0)
-       }
+    private func setAspectRatio(width: CGFloat, height: CGFloat) {
+        if height == 0 || width == 0 {
+            return
+        }
+        self.imageHeightConstraint?.isActive = false
+        self.imageWidthConstraint?.isActive = false
+        self.imageWidthConstraint = self.contentImageView.widthAnchor.constraint(lessThanOrEqualToConstant: width)
+        self.imageHeightConstraint = self.contentImageView.heightAnchor.constraint(
+            lessThanOrEqualTo: self.contentImageView.widthAnchor,
+            multiplier: height / width
+        )
+        self.imageHeightConstraint?.isActive = true
+        self.imageWidthConstraint?.isActive = true
+    }
 
-       self.imageWidthConstraint = self.contentImageView.widthAnchor.constraint(lessThanOrEqualToConstant: messageWidth)
-       self.imageHeightConstraint = self.contentImageView.heightAnchor.constraint(
-           lessThanOrEqualTo: self.contentImageView.widthAnchor,
-           multiplier: messageHeight / messageWidth
-       )
-       self.imageHeightConstraint?.isActive = true
-       self.imageWidthConstraint?.isActive = true
+    private func setAspectRatioFor(message: DcMsg) {
+        var width = message.messageWidth
+        var height = message.messageHeight
+        if width == 0 || height == 0,
+           let image = message.image {
+            width = image.size.width
+            height = image.size.height
+            message.setLateFilingMediaSize(width: width, height: height, duration: 0)
+        }
+        setAspectRatio(width: width, height: height)
+    }
 
- }
+    private func setAspectRatioFor(message: DcMsg, with image: UIImage?, isPlaceholder: Bool) {
+        var width = message.messageWidth
+        var height = message.messageHeight
+        if width == 0 || height == 0,
+           let image = image {
+            width = image.size.width
+            height = image.size.height
+            if !isPlaceholder {
+                message.setLateFilingMediaSize(width: width, height: height, duration: 0)
+            }
+        }
+        setAspectRatio(width: width, height: height)
+    }
 
     override func prepareForReuse() {
         contentImageView.image = nil
         messageLabel.text = nil
         messageLabel.attributedText = nil
+        tag = -1
     }
 }

+ 122 - 0
deltachat-ios/Chat/Views/NewPlayButtonView.swift

@@ -0,0 +1,122 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+open class NewPlayButtonView: UIView {
+
+    // MARK: - Properties
+
+    public let triangleView = UIView()
+
+    private var triangleCenterXConstraint: NSLayoutConstraint?
+    private var cacheFrame: CGRect = .zero
+
+    // MARK: - Initializers
+
+    public override init(frame: CGRect) {
+        super.init(frame: frame)
+
+        setupSubviews()
+        setupConstraints()
+        setupView()
+    }
+
+    required public init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+
+        setupSubviews()
+        setupConstraints()
+        setupView()
+    }
+
+    // MARK: - Methods
+
+    open override func layoutSubviews() {
+        super.layoutSubviews()
+
+        guard !cacheFrame.equalTo(frame) else { return }
+        cacheFrame = frame
+
+        updateTriangleConstraints()
+        applyCornerRadius()
+        applyTriangleMask()
+    }
+
+    private func setupSubviews() {
+        addSubview(triangleView)
+    }
+
+    private func setupView() {
+        triangleView.clipsToBounds = true
+        triangleView.backgroundColor = .black
+
+        backgroundColor = .playButtonLightGray
+    }
+
+    private func setupConstraints() {
+        triangleView.translatesAutoresizingMaskIntoConstraints = false
+
+        let centerX = triangleView.centerXAnchor.constraint(equalTo: centerXAnchor)
+        let centerY = triangleView.centerYAnchor.constraint(equalTo: centerYAnchor)
+        let width = triangleView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.5)
+        let height = triangleView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.5)
+
+        triangleCenterXConstraint = centerX
+
+        NSLayoutConstraint.activate([centerX, centerY, width, height])
+    }
+
+    private func triangleMask(for frame: CGRect) -> CAShapeLayer {
+        let shapeLayer = CAShapeLayer()
+        let trianglePath = UIBezierPath()
+
+        let point1 = CGPoint(x: frame.minX, y: frame.minY)
+        let point2 = CGPoint(x: (frame.maxX/5) * 4, y: frame.maxY/2)
+        let point3 = CGPoint(x: frame.minX, y: frame.maxY)
+
+        trianglePath .move(to: point1)
+        trianglePath .addLine(to: point2)
+        trianglePath .addLine(to: point3)
+        trianglePath .close()
+
+        shapeLayer.path = trianglePath.cgPath
+
+        return shapeLayer
+    }
+
+    private func updateTriangleConstraints() {
+        triangleCenterXConstraint?.constant = frame.width/8
+    }
+
+    private func applyTriangleMask() {
+        let rect = CGRect(origin: .zero, size: triangleView.bounds.size)
+        triangleView.layer.mask = triangleMask(for: rect)
+    }
+
+    private func applyCornerRadius() {
+        layer.cornerRadius = frame.width / 2
+    }
+
+}