Prechádzať zdrojové kódy

Merge pull request #1245 from deltachat/stickers_merge

Stickers merge
cyBerta 4 rokov pred
rodič
commit
e4f2beda3d

+ 1 - 1
DcCore/DcCore/DC/Wrapper.swift

@@ -945,7 +945,7 @@ public class DcMsg {
 
     public lazy var image: UIImage? = { [weak self] in
         let filetype = dc_msg_get_viewtype(messagePointer)
-        if let path = fileURL, filetype == DC_MSG_IMAGE || filetype == DC_MSG_GIF {
+        if let path = fileURL, filetype == DC_MSG_IMAGE || filetype == DC_MSG_GIF || filetype == DC_MSG_STICKER {
             if path.isFileURL {
                 do {
                     let data = try Data(contentsOf: path)

+ 26 - 3
DcShare/Controller/ShareViewController.swift

@@ -3,6 +3,8 @@ import Social
 import DcCore
 import MobileCoreServices
 import Intents
+import SDWebImageWebPCoder
+import SDWebImage
 
 
 class ShareViewController: SLComposeServiceViewController {
@@ -37,11 +39,19 @@ class ShareViewController: SLComposeServiceViewController {
     var shareAttachment: ShareAttachment?
     var isAccountConfigured: Bool = true
 
-    lazy var preview: UIImageView? = {
-        let imageView = UIImageView(frame: .zero)
+    var previewImageHeightConstraint: NSLayoutConstraint?
+    var previewImageWidthConstraint: NSLayoutConstraint?
+
+    lazy var preview: SDAnimatedImageView? = {
+        let imageView = SDAnimatedImageView(frame: .zero)
         imageView.clipsToBounds = true
         imageView.shouldGroupAccessibilityChildren = true
         imageView.isAccessibilityElement = false
+        imageView.contentMode = .scaleAspectFit
+        previewImageHeightConstraint = imageView.constraintHeightTo(96)
+        previewImageWidthConstraint = imageView.constraintWidthTo(96)
+        previewImageHeightConstraint?.isActive = true
+        previewImageWidthConstraint?.isActive = true
         return imageView
     }()
 
@@ -57,6 +67,8 @@ class ShareViewController: SLComposeServiceViewController {
             }
         }
         placeholder = String.localized("chat_input_placeholder")
+        let webPCoder = SDImageWebPCoder.shared
+        SDImageCodersManager.shared.addCoder(webPCoder)
 
     }
 
@@ -201,9 +213,20 @@ extension ShareViewController: ShareAttachmentDelegate {
     }
 
     func onThumbnailChanged() {
-        DispatchQueue.main.async {
+        DispatchQueue.main.async { [weak self] in
+            guard let self = self else { return }
             if let preview = self.preview {
                 preview.image = self.shareAttachment?.thumbnail ?? nil
+
+                if let image = preview.image, image.sd_imageFormat == .webP {
+                    self.previewImageWidthConstraint?.isActive = false
+                    self.previewImageHeightConstraint?.isActive = false
+                    preview.centerInSuperview()
+                    self.textView.text = nil
+                    self.textView.attributedText = nil
+                    self.placeholder = nil
+                    self.textView.isHidden = true
+                }
             }
         }
     }

+ 19 - 10
DcShare/Helper/ShareAttachment.swift

@@ -43,12 +43,12 @@ class ShareAttachment {
         guard let items = inputItems as? [NSExtensionItem] else { return }
         for item in items {
             if let attachments = item.attachments {
-                createMessageFromDataRepresentaion(attachments)
+                createMessageFromDataRepresentation(attachments)
             }
         }
     }
 
-    private func createMessageFromDataRepresentaion(_ attachments: [NSItemProvider]) {
+    private func createMessageFromDataRepresentation(_ attachments: [NSItemProvider]) {
         for attachment in attachments {
             if attachment.hasItemConformingToTypeIdentifier(kUTTypeGIF as String) {
                 createAnimatedImageMsg(attachment)
@@ -85,7 +85,7 @@ class ShareAttachment {
                 self.messages.append(msg)
                 self.delegate?.onAttachmentChanged()
                 if self.imageThumbnail == nil {
-                    self.imageThumbnail = result.scaleDownImage(toMax: self.thumbnailSize)
+                    self.imageThumbnail = result
                     self.delegate?.onThumbnailChanged()
                 }
                 if let error = error {
@@ -97,26 +97,35 @@ class ShareAttachment {
 
     private func createImageMsg(_ item: NSItemProvider) {
         item.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil) { data, error in
-            let result: UIImage?
+            var result: UIImage?
             switch data {
             case let image as UIImage:
                 result = image
             case let data as Data:
-                result = UIImage(data: data)
+                result = SDAnimatedImage(data: data)
             case let url as URL:
-                result = UIImage(contentsOfFile: url.path)
+                result = SDAnimatedImage(contentsOfFile: url.path)
             default:
                 self.dcContext.logger?.debug("Unexpected data: \(type(of: data))")
                 result = nil
             }
             if let result = result {
-                let path = DcUtils.saveImage(image: result)
-                let msg = DcMsg(viewType: DC_MSG_IMAGE)
+                var msg: DcMsg
+                var path: String?
+                if result.sd_imageFormat == .webP,
+                   let imageData = result.sd_imageData() {
+                    path = DcUtils.saveImage(data: imageData, suffix: "webp")
+                    msg = DcMsg(viewType: DC_MSG_STICKER)
+                } else {
+                    path = DcUtils.saveImage(image: result)
+                    msg = DcMsg(viewType: DC_MSG_IMAGE)
+                }
+
                 msg.setFile(filepath: path)
                 self.messages.append(msg)
                 self.delegate?.onAttachmentChanged()
                 if self.imageThumbnail == nil {
-                    self.imageThumbnail = result.scaleDownImage(toMax: self.thumbnailSize)
+                    self.imageThumbnail = result
                     self.delegate?.onThumbnailChanged()
                 }
             }
@@ -134,7 +143,7 @@ class ShareAttachment {
                 self.delegate?.onAttachmentChanged()
                 if self.imageThumbnail == nil {
                     DispatchQueue.global(qos: .background).async {
-                        self.imageThumbnail = DcUtils.generateThumbnailFromVideo(url: url)?.scaleDownImage(toMax: self.thumbnailSize)
+                        self.imageThumbnail = DcUtils.generateThumbnailFromVideo(url: url)
                         DispatchQueue.main.async {
                             self.delegate?.onThumbnailChanged()
                         }

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

@@ -57,6 +57,7 @@
 		3060119C22DDE24000C1CE6F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3060119E22DDE24000C1CE6F /* Localizable.strings */; };
 		306011B622E5E7FB00C1CE6F /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 306011B422E5E7FB00C1CE6F /* Localizable.stringsdict */; };
 		30653081254358B10093E196 /* QuoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30653080254358B10093E196 /* QuoteView.swift */; };
+		3067AA4C2666310E00525036 /* ChatInputTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3067AA4B2666310E00525036 /* ChatInputTextView.swift */; };
 		306C32322445CDE9001D89F3 /* DcLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 306C32312445CDE9001D89F3 /* DcLogger.swift */; };
 		30734326249A280B00BF9AD1 /* MediaQualityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30734325249A280B00BF9AD1 /* MediaQualityController.swift */; };
 		307A82CC25B8D26700748B57 /* ChatEditingBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307A82CB25B8D26700748B57 /* ChatEditingBar.swift */; };
@@ -300,6 +301,7 @@
 		306011C822E5E83100C1CE6F /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
 		306011C922E5E83500C1CE6F /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
 		30653080254358B10093E196 /* QuoteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuoteView.swift; sourceTree = "<group>"; };
+		3067AA4B2666310E00525036 /* ChatInputTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInputTextView.swift; sourceTree = "<group>"; };
 		306C32312445CDE9001D89F3 /* DcLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DcLogger.swift; sourceTree = "<group>"; };
 		30734325249A280B00BF9AD1 /* MediaQualityController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaQualityController.swift; sourceTree = "<group>"; };
 		307A82CB25B8D26700748B57 /* ChatEditingBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatEditingBar.swift; sourceTree = "<group>"; };
@@ -588,6 +590,7 @@
 				30FDB6B624D193DD0066C48D /* Cells */,
 				30E348DE24F3F819005C93D1 /* ChatTableView.swift */,
 				303492CE2587C2DC00A523D0 /* ChatInputBar.swift */,
+				3067AA4B2666310E00525036 /* ChatInputTextView.swift */,
 				307A82CB25B8D26700748B57 /* ChatEditingBar.swift */,
 				302E1BB3252B5AB4008F4264 /* PlayButtonView.swift */,
 				30F8817524DA97DA0023780E /* BackgroundContainer.swift */,
@@ -1323,6 +1326,7 @@
 				AEFBE22F23FEF23D0045327A /* ProviderInfoCell.swift in Sources */,
 				AE6EC5242497663200A400E4 /* UIImageView+Extensions.swift in Sources */,
 				30F8817624DA97DA0023780E /* BackgroundContainer.swift in Sources */,
+				3067AA4C2666310E00525036 /* ChatInputTextView.swift in Sources */,
 				30B0ACFA24AB5B99004D5E29 /* SettingsEphemeralMessageController.swift in Sources */,
 				B20462E42440A4A600367A57 /* SettingsAutodelOverviewController.swift in Sources */,
 				305962102346154D00C80F33 /* String+Extension.swift in Sources */,

+ 4 - 0
deltachat-ios/AppDelegate.swift

@@ -5,6 +5,7 @@ import UIKit
 import UserNotifications
 import DcCore
 import DBDebugToolkit
+import SDWebImageWebPCoder
 
 let logger = SwiftyBeaver.self
 
@@ -105,6 +106,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
             registerForNotifications()
         }
 
+        let webPCoder = SDImageWebPCoder.shared
+        SDImageCodersManager.shared.addCoder(webPCoder)
+
         return true
     }
 

+ 22 - 2
deltachat-ios/Chat/ChatViewController.swift

@@ -553,7 +553,7 @@ class ChatViewController: UITableViewController {
         }
 
         let cell: BaseMessageCell
-        if message.type == DC_MSG_IMAGE || message.type == DC_MSG_GIF || message.type == DC_MSG_VIDEO {
+        if message.type == DC_MSG_IMAGE || message.type == DC_MSG_GIF || message.type == DC_MSG_VIDEO || message.type == DC_MSG_STICKER {
             cell = tableView.dequeueReusableCell(withIdentifier: "image", for: indexPath) as? ImageTextCell ?? ImageTextCell()
         } else if message.type == DC_MSG_FILE {
             if message.isSetupMessage {
@@ -890,6 +890,9 @@ class ChatViewController: UITableViewController {
         messageInputBar.inputTextView.scrollIndicatorInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
         configureInputBarItems()
         messageInputBar.inputTextView.delegate = self
+        if let inputTextView = messageInputBar.inputTextView as? ChatInputTextView {
+            inputTextView.imagePasteDelegate = self
+        }
     }
 
     private func evaluateInputBar(draft: DraftModel) {
@@ -1262,6 +1265,14 @@ class ChatViewController: UITableViewController {
         }
     }
 
+    private func sendSticker(_ image: UIImage) {
+        DispatchQueue.global().async {
+            if let path = DcUtils.saveImage(image: image) {
+                self.sendAttachmentMessage(viewType: DC_MSG_STICKER, filePath: path, message: nil)
+            }
+        }
+    }
+
     private func sendAttachmentMessage(viewType: Int32, filePath: String, message: String? = nil, quoteMessage: DcMsg? = nil) {
         let msg = DcMsg(viewType: viewType)
         msg.setFile(filepath: filePath)
@@ -1317,7 +1328,9 @@ class ChatViewController: UITableViewController {
     func showMediaGalleryFor(indexPath: IndexPath) {
         let messageId = messageIds[indexPath.row]
         let message = DcMsg(id: messageId)
-        showMediaGalleryFor(message: message)
+        if message.type != DC_MSG_STICKER {
+            showMediaGalleryFor(message: message)
+        }
     }
 
     func showMediaGalleryFor(message: DcMsg) {
@@ -1643,3 +1656,10 @@ extension ChatViewController: UITextViewDelegate {
         return true
     }
 }
+
+// MARK: - ChatInputTextViewPasteDelegate
+extension ChatViewController: ChatInputTextViewPasteDelegate {
+    func onImagePasted(image: UIImage) {
+        sendSticker(image)
+    }
+}

+ 20 - 5
deltachat-ios/Chat/Views/Cells/BaseMessageCell.swift

@@ -66,6 +66,8 @@ public class BaseMessageCell: UITableViewCell {
         }
     }
 
+    public var isTransparent: Bool = false
+
     public weak var baseDelegate: BaseMessageCellDelegate?
 
     public lazy var quoteView: QuoteView = {
@@ -311,7 +313,7 @@ public class BaseMessageCell: UITableViewCell {
         isFullMessageButtonHidden = !msg.hasHtml
 
         messageBackgroundContainer.update(rectCorners: messageStyle,
-                                          color: msg.isFromCurrentSender ? DcColors.messagePrimaryColor : DcColors.messageSecondaryColor)
+                                          color: getBackgroundColor(message: msg))
 
         if !msg.isInfo {
             bottomLabel.attributedText = getFormattedBottomLine(message: msg)
@@ -369,6 +371,18 @@ public class BaseMessageCell: UITableViewCell {
             "\(getFormattedBottomLineAccessibilityString(message: message))"
     }
 
+    func getBackgroundColor(message: DcMsg) -> UIColor {
+        var backgroundColor: UIColor
+        if isTransparent {
+            backgroundColor = UIColor.init(alpha: 0, red: 0, green: 0, blue: 0)
+        } else if message.isFromCurrentSender {
+            backgroundColor =  DcColors.messagePrimaryColor
+        } else {
+            backgroundColor = DcColors.messageSecondaryColor
+        }
+        return backgroundColor
+    }
+
     func getFormattedBottomLineAccessibilityString(message: DcMsg) -> String {
         let padlock =  message.showPadlock() ? "\(String.localized("encrypted_message")), " : ""
         let date = "\(message.formattedSentDate()), "
@@ -391,21 +405,22 @@ public class BaseMessageCell: UITableViewCell {
 
         let text = NSMutableAttributedString()
         if message.fromContactId == Int(DC_CONTACT_ID_SELF) {
+            let tintColor: UIColor? = !(bottomCompactView || isTransparent) ? DcColors.checkmarkGreen : nil
             if let style = NSMutableParagraphStyle.default.mutableCopy() as? NSMutableParagraphStyle {
                 style.alignment = .right
                 timestampAttributes[.paragraphStyle] = style
-                if !bottomCompactView {
-                    timestampAttributes[.foregroundColor] = DcColors.checkmarkGreen
+                if let tintColor = tintColor {
+                    timestampAttributes[.foregroundColor] = tintColor
                 }
             }
 
             text.append(NSAttributedString(string: message.formattedSentDate(), attributes: timestampAttributes))
             if message.showPadlock() {
-                attachPadlock(to: text, color: bottomCompactView ? nil : DcColors.checkmarkGreen)
+                attachPadlock(to: text, color: tintColor)
             }
             
             if message.hasLocation {
-                attachLocation(to: text, color: bottomCompactView ? nil : DcColors.checkmarkGreen)
+                attachLocation(to: text, color: tintColor)
             }
 
             attachSendingState(message.state, to: text)

+ 48 - 9
deltachat-ios/Chat/Views/Cells/ImageTextCell.swift

@@ -44,16 +44,17 @@ class ImageTextCell: BaseMessageCell {
 
     override func update(msg: DcMsg, messageStyle: UIRectCorner, showAvatar: Bool, showName: Bool) {
         messageLabel.text = msg.text
-        bottomCompactView = msg.text?.isEmpty ?? true
+        bottomCompactView = msg.type != DC_MSG_STICKER && msg.text?.isEmpty ?? true
         mainContentView.spacing = msg.text?.isEmpty ?? false ? 0 : 6
         topCompactView = msg.quoteText == nil ? true : false
+        isTransparent = msg.type == DC_MSG_STICKER
+        topLabel.isHidden = msg.type == DC_MSG_STICKER
         tag = msg.id
-        if msg.type == DC_MSG_IMAGE, let image = msg.image {
-            contentImageView.image = image
-            accessibilityLabel = String.localized("image")
-            playButtonView.isHidden = true
-            setAspectRatioFor(message: msg)
-        } else if msg.type == DC_MSG_GIF, let url = msg.fileURL {
+
+        if let url = msg.fileURL,
+           (msg.type == DC_MSG_IMAGE ||
+            msg.type == DC_MSG_GIF ||
+                msg.type == DC_MSG_STICKER) {
             contentImageView.sd_setImage(with: url,
                                          placeholderImage: UIImage(color: UIColor.init(alpha: 0,
                                                                                        red: 255,
@@ -61,7 +62,7 @@ class ImageTextCell: BaseMessageCell {
                                                                                        blue: 255),
                                                                    size: CGSize(width: 500, height: 500)))
             playButtonView.isHidden = true
-            accessibilityLabel = String.localized("gif")
+            accessibilityLabel = msg.type == DC_MSG_GIF ? String.localized("gif") : String.localized("image")
             setAspectRatioFor(message: msg)
         } else if msg.type == DC_MSG_VIDEO, let url = msg.fileURL {
             playButtonView.isHidden = false
@@ -100,6 +101,39 @@ class ImageTextCell: BaseMessageCell {
         }
     }
 
+    private func setStickerAspectRatio(width: CGFloat, height: CGFloat) {
+        if height == 0 || width == 0 {
+            return
+        }
+        var width = width
+        var height = height
+
+        self.imageHeightConstraint?.isActive = false
+        self.imageWidthConstraint?.isActive = false
+
+        // check if sticker has the allowed minimal width
+        if width < minImageWidth {
+            height = (height / width) * minImageWidth
+            width = minImageWidth
+        }
+
+        // check if sticker has the allowed maximal width
+        let maxWidth  = min(UIScreen.main.bounds.height, UIScreen.main.bounds.width) / 2
+        if width > maxWidth {
+            height = (height / width) * maxWidth
+            width = maxWidth
+        }
+
+        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
+    }
+
     private func setAspectRatio(width: CGFloat, height: CGFloat) {
         if height == 0 || width == 0 {
             return
@@ -165,9 +199,14 @@ class ImageTextCell: BaseMessageCell {
             height = image.size.height
             message.setLateFilingMediaSize(width: width, height: height, duration: 0)
         }
-        setAspectRatio(width: width, height: height)
+        if message.type == DC_MSG_STICKER {
+            setStickerAspectRatio(width: width, height: height)
+        } else {
+            setAspectRatio(width: width, height: height)
+        }
     }
 
+
     private func setAspectRatioFor(message: DcMsg, with image: UIImage?, isPlaceholder: Bool) {
         var width = message.messageWidth
         var height = message.messageHeight

+ 11 - 0
deltachat-ios/Chat/Views/ChatInputBar.swift

@@ -24,6 +24,17 @@ public class ChatInputBar: InputBarAccessoryView {
         backgroundColor = DcColors.chatBackgroundColor
     }
 
+    override open func setup() {
+        replaceInputBar()
+        super.setup()
+    }
+
+    func replaceInputBar() {
+        inputTextView = ChatInputTextView()
+        inputTextView.translatesAutoresizingMaskIntoConstraints = false
+        inputTextView.inputBarAccessoryView = self
+    }
+
     func setupKeyboardObserver() {
         NotificationCenter.default.addObserver(
             self,

+ 27 - 0
deltachat-ios/Chat/Views/ChatInputTextView.swift

@@ -0,0 +1,27 @@
+import Foundation
+import InputBarAccessoryView
+
+public class ChatInputTextView: InputTextView {
+
+    public weak var imagePasteDelegate: ChatInputTextViewPasteDelegate?
+
+    // MARK: - Image Paste Support
+    open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
+        if action == NSSelectorFromString("paste:") && UIPasteboard.general.image != nil {
+            return true
+        }
+        return super.canPerformAction(action, withSender: sender)
+    }
+
+    open override func paste(_ sender: Any?) {
+        guard let image = UIPasteboard.general.image else {
+            return super.paste(sender)
+        }
+        imagePasteDelegate?.onImagePasted(image: image)
+    }
+}
+
+
+public protocol ChatInputTextViewPasteDelegate: class {
+    func onImagePasted(image: UIImage)
+}

+ 7 - 3
deltachat-ios/Model/GalleryItem.swift

@@ -43,17 +43,21 @@ class GalleryItem: ContextMenuItem {
         }
         switch viewtype {
         case .image:
-            thumbnailImage = msg.image
+            if url.pathExtension == "webp" {
+                loadAsyncImageThumbnail(from: url)
+            } else {
+                thumbnailImage = msg.image
+            }
         case .video:
             loadVideoThumbnail(from: url)
         case .gif:
-            loadGifThumbnail(from: url)
+            loadAsyncImageThumbnail(from: url)
         default:
             safe_fatalError("unsupported viewtype - viewtype \(viewtype) not supported.")
         }
     }
 
-    private func loadGifThumbnail(from url: URL) {
+    private func loadAsyncImageThumbnail(from url: URL) {
         DispatchQueue.global(qos: .userInteractive).async {
             guard let imageData = try? Data(contentsOf: url) else {
                 return