瀏覽代碼

initial commit for a smaller document view in chats

cyberta 5 年之前
父節點
當前提交
04219e76e7

+ 13 - 1
deltachat-ios.xcodeproj/project.pbxproj

@@ -93,6 +93,9 @@
 		306011B622E5E7FB00C1CE6F /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 306011B422E5E7FB00C1CE6F /* Localizable.stringsdict */; };
 		306C32322445CDE9001D89F3 /* DcLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 306C32312445CDE9001D89F3 /* DcLogger.swift */; };
 		307D822E241669C7006D2490 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307D822D241669C7006D2490 /* LocationManager.swift */; };
+		308FEA4C2462F11300FCEAD6 /* FileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 308FEA4B2462F11300FCEAD6 /* FileView.swift */; };
+		308FEA50246AB67100FCEAD6 /* FileMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 308FEA4F246AB67100FCEAD6 /* FileMessageCell.swift */; };
+		308FEA52246ABA2700FCEAD6 /* FileMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 308FEA51246ABA2700FCEAD6 /* FileMessageSizeCalculator.swift */; };
 		3095A351237DD1F700AB07F7 /* MediaPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3095A350237DD1F700AB07F7 /* MediaPicker.swift */; };
 		30A4D9AE2332672700544344 /* QrInviteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A4D9AD2332672600544344 /* QrInviteViewController.swift */; };
 		30C0D49D237C4908008E2A0E /* CertificateCheckController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C0D49C237C4908008E2A0E /* CertificateCheckController.swift */; };
@@ -372,6 +375,9 @@
 		306011C922E5E83500C1CE6F /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
 		306C32312445CDE9001D89F3 /* DcLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DcLogger.swift; sourceTree = "<group>"; };
 		307D822D241669C7006D2490 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = "<group>"; };
+		308FEA4B2462F11300FCEAD6 /* FileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileView.swift; sourceTree = "<group>"; };
+		308FEA4F246AB67100FCEAD6 /* FileMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileMessageCell.swift; sourceTree = "<group>"; };
+		308FEA51246ABA2700FCEAD6 /* FileMessageSizeCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileMessageSizeCalculator.swift; sourceTree = "<group>"; };
 		3095A350237DD1F700AB07F7 /* MediaPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPicker.swift; sourceTree = "<group>"; };
 		30A4D9AD2332672600544344 /* QrInviteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QrInviteViewController.swift; sourceTree = "<group>"; };
 		30AC265E237F1807002A943F /* AvatarHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarHelper.swift; sourceTree = "<group>"; };
@@ -653,9 +659,9 @@
 		305961AC2346125100C80F33 /* Views */ = {
 			isa = PBXGroup;
 			children = (
+				305961B72346125100C80F33 /* HeadersFooters */,
 				305961AD2346125100C80F33 /* Cells */,
 				305961B62346125100C80F33 /* MessageLabel.swift */,
-				305961B72346125100C80F33 /* HeadersFooters */,
 				305961B92346125100C80F33 /* TypingIndicator.swift */,
 				305961BA2346125100C80F33 /* MessageContainerView.swift */,
 				305961BB2346125100C80F33 /* TypingBubble.swift */,
@@ -665,6 +671,7 @@
 				305961BF2346125100C80F33 /* PlayButtonView.swift */,
 				305961C02346125100C80F33 /* BubbleCircle.swift */,
 				3040F461234F550300FA34D5 /* AudioPlayerView.swift */,
+				308FEA4B2462F11300FCEAD6 /* FileView.swift */,
 			);
 			path = Views;
 			sourceTree = "<group>";
@@ -678,6 +685,7 @@
 				305961AF2346125100C80F33 /* LocationMessageCell.swift */,
 				305961B02346125100C80F33 /* MediaMessageCell.swift */,
 				305961B12346125100C80F33 /* TextMessageCell.swift */,
+				308FEA4F246AB67100FCEAD6 /* FileMessageCell.swift */,
 				305961B22346125100C80F33 /* TypingIndicatorCell.swift */,
 				305961B32346125100C80F33 /* MessageContentCell.swift */,
 				305961B42346125100C80F33 /* MessageCollectionViewCell.swift */,
@@ -704,6 +712,7 @@
 				305961C62346125100C80F33 /* MessagesCollectionViewFlowLayout.swift */,
 				305961C72346125100C80F33 /* MediaMessageSizeCalculator.swift */,
 				300C50A0234BDAB800F8AE22 /* TextMediaMessageSizeCalculator.swift */,
+				308FEA51246ABA2700FCEAD6 /* FileMessageSizeCalculator.swift */,
 				305961C82346125100C80F33 /* AudioMessageSizeCalculator.swift */,
 				305961C92346125100C80F33 /* TextMessageSizeCalculator.swift */,
 				305961CA2346125100C80F33 /* LocationMessageSizeCalculator.swift */,
@@ -1358,6 +1367,7 @@
 				AE0D26FD1FB1FE88002FAFCE /* ChatListController.swift in Sources */,
 				30149D9322F21129003C12B5 /* QrViewController.swift in Sources */,
 				AEE56D80225504DB007DC082 /* Extensions.swift in Sources */,
+				308FEA50246AB67100FCEAD6 /* FileMessageCell.swift in Sources */,
 				7A0052C81FBE6CB40048C3BF /* NewContactController.swift in Sources */,
 				AEE56D762253431E007DC082 /* AccountSetupController.swift in Sources */,
 				305FE03623A81B4C0053BE90 /* PaddingLabel.swift in Sources */,
@@ -1369,6 +1379,7 @@
 				305961CC2346125100C80F33 /* UIView+Extensions.swift in Sources */,
 				7A9FB1441FB061E2001FEA36 /* AppDelegate.swift in Sources */,
 				AE76E5EE242BF2EA003CF461 /* WelcomeViewController.swift in Sources */,
+				308FEA52246ABA2700FCEAD6 /* FileMessageSizeCalculator.swift in Sources */,
 				305961F52346125100C80F33 /* TypingIndicatorCell.swift in Sources */,
 				AEE56D7D2253ADB4007DC082 /* HudHandler.swift in Sources */,
 				305961FF2346125100C80F33 /* AvatarView.swift in Sources */,
@@ -1391,6 +1402,7 @@
 				305962092346125100C80F33 /* AudioMessageSizeCalculator.swift in Sources */,
 				305961DB2346125100C80F33 /* AudioItem.swift in Sources */,
 				305962012346125100C80F33 /* PlayButtonView.swift in Sources */,
+				308FEA4C2462F11300FCEAD6 /* FileView.swift in Sources */,
 				B20462E42440A4A600367A57 /* SettingsAutodelOverviewController.swift in Sources */,
 				789E879D21D6DF86003ED1C5 /* ProgressHud.swift in Sources */,
 				305961F32346125100C80F33 /* MediaMessageCell.swift in Sources */,

+ 5 - 1
deltachat-ios/Controller/ChatViewController.swift

@@ -527,10 +527,14 @@ class ChatViewController: MessagesViewController {
             let cell = messagesCollectionView.dequeueReusableCell(MediaMessageCell.self, for: indexPath)
             cell.configure(with: message, at: indexPath, and: messagesCollectionView)
             return cell
-        case .photoText, .videoText, .fileText:
+        case .photoText, .videoText:
             let cell = messagesCollectionView.dequeueReusableCell(TextMediaMessageCell.self, for: indexPath)
             cell.configure(with: message, at: indexPath, and: messagesCollectionView)
             return cell
+        case .fileText:
+            let cell = messagesCollectionView.dequeueReusableCell(FileMessageCell.self, for: indexPath)
+            cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+            return cell
         case .location:
             let cell = messagesCollectionView.dequeueReusableCell(LocationMessageCell.self, for: indexPath)
             cell.configure(with: message, at: indexPath, and: messagesCollectionView)

+ 15 - 12
deltachat-ios/DC/DcMsg+Extension.swift

@@ -52,7 +52,7 @@ extension DcMsg: MessageType {
         }
         let attributedString = NSAttributedString(string: text, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16.0),
                                                                              NSAttributedString.Key.foregroundColor: DcColors.defaultTextColor])
-        return MessageKind.videoText(Media(url: fileURL, image: thumbnail, text: attributedString))
+        return MessageKind.videoText(Media(url: fileURL, image: thumbnail, text: [attributedString]))
     }
 
     internal func createImageMessage(text: String) -> MessageKind {
@@ -61,7 +61,7 @@ extension DcMsg: MessageType {
         }
         let attributedString = NSAttributedString(string: text, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16.0),
                                                                              NSAttributedString.Key.foregroundColor: DcColors.defaultTextColor])
-        return MessageKind.photoText(Media(image: image, text: attributedString))
+        return MessageKind.photoText(Media(image: image, text: [attributedString]))
     }
 
     internal func createAudioMessage(text: String) -> MessageKind {
@@ -76,18 +76,21 @@ extension DcMsg: MessageType {
     }
 
     internal func createFileMessage(text: String) -> MessageKind {
-        let fileString = "\(self.filename ?? "???") (\(self.filesize / 1024) kB)"
-        let attributedFileString = NSMutableAttributedString(string: fileString,
+        let fileString = "\(self.filename ?? "???")"
+        let fileSizeString = "(\(self.filesize / 1024) kB)"
+        let attributedMediaMessageString =
+                   NSAttributedString(string: text,
+                                             attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16.0),
+                                                          NSAttributedString.Key.foregroundColor: DcColors.defaultTextColor])
+        let attributedFileString = NSAttributedString(string: fileString,
                                                              attributes: [NSAttributedString.Key.font: UIFont.italicSystemFont(ofSize: 13.0),
                                                                           NSAttributedString.Key.foregroundColor: DcColors.defaultTextColor])
-        if !text.isEmpty {
-            attributedFileString.append(NSAttributedString(string: "\n\n",
-                                                           attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 7.0)]))
-            attributedFileString.append(NSAttributedString(string: text,
-                                                           attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16.0),
-                                                                        NSAttributedString.Key.foregroundColor: DcColors.defaultTextColor]))
-        }
-        return MessageKind.fileText(Media(text: attributedFileString))
+        let attributedFileSizeString = NSAttributedString(string: fileSizeString,
+                                                                 attributes: [NSAttributedString.Key.font: UIFont.italicSystemFont(ofSize: 13.0),
+                                                                              NSAttributedString.Key.foregroundColor: DcColors.defaultTextColor])
+
+        let mediaText = [attributedMediaMessageString, attributedFileString, attributedFileSizeString]
+        return MessageKind.fileText(Media(placeholderImage: UIImage(named: "ic_attach_file_36pt"), text: mediaText))
     }
     
 }

+ 7 - 5
deltachat-ios/MessageKit/Controllers/MessagesViewController.swift

@@ -288,10 +288,14 @@ UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
             let cell = messagesCollectionView.dequeueReusableCell(MediaMessageCell.self, for: indexPath)
             cell.configure(with: message, at: indexPath, and: messagesCollectionView)
             return cell
-        case .photoText, .videoText, .fileText:
+        case .photoText, .videoText:
             let cell = messagesCollectionView.dequeueReusableCell(TextMediaMessageCell.self, for: indexPath)
             cell.configure(with: message, at: indexPath, and: messagesCollectionView)
             return cell
+        case .fileText:
+            let cell = messagesCollectionView.dequeueReusableCell(FileMessageCell.self, for: indexPath)
+            cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+            return cell
         case .location:
             let cell = messagesCollectionView.dequeueReusableCell(LocationMessageCell.self, for: indexPath)
             cell.configure(with: message, at: indexPath, and: messagesCollectionView)
@@ -416,10 +420,8 @@ UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
             pasteBoard.string = text
         case .attributedText(let attributedText):
             pasteBoard.string = attributedText.string
-        case .photoText(let mediaItem), .videoText(let mediaItem):
-            pasteBoard.string = mediaItem.text?.string
-        case .fileText(let mediaItem):
-            pasteBoard.string = mediaItem.text?.string
+        case .photoText(let mediaItem), .videoText(let mediaItem), .fileText(let mediaItem):
+            pasteBoard.string = mediaItem.text?[MediaItemConstants.messageText].string
         default:
             break
         }

+ 109 - 0
deltachat-ios/MessageKit/Layout/FileMessageSizeCalculator.swift

@@ -0,0 +1,109 @@
+import Foundation
+import UIKit
+
+open class FileMessageSizeCalculator: MessageSizeCalculator {
+
+    let defaultFileMessageCellWidth = 250
+    let defaultFileMessageCellHeight = 100
+
+    public var incomingMessageLabelInsets = UIEdgeInsets(top: FileMessageCell.insetTop,
+                                                         left: FileMessageCell.insetHorizontalBig,
+                                                         bottom: FileMessageCell.insetBottom,
+                                                         right: FileMessageCell.insetHorizontalSmall)
+    public var outgoingMessageLabelInsets = UIEdgeInsets(top: FileMessageCell.insetTop,
+                                                         left: FileMessageCell.insetHorizontalSmall,
+                                                         bottom: FileMessageCell.insetBottom,
+                                                         right: FileMessageCell.insetHorizontalBig)
+
+    public var messageLabelFont = UIFont.preferredFont(forTextStyle: .body)
+
+    internal func messageLabelInsets(for message: MessageType) -> UIEdgeInsets {
+        let dataSource = messagesLayout.messagesDataSource
+        let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
+        return isFromCurrentSender ? outgoingMessageLabelInsets : incomingMessageLabelInsets
+    }
+
+    open override func messageContainerSize(for message: MessageType) -> CGSize {
+
+
+        let sizeForMediaItem = { (maxWidth: CGFloat, item: MediaItem) -> CGSize in
+            var maxMediaTextWidth: CGFloat = 0  // width of the attached text message
+            var maxMediaTitleWidth: CGFloat = 0 // width of the file name text & file size text
+            var mediaTitleHeight: CGFloat = 0
+            var mediaSubtitleHeight: CGFloat = 0
+            var messageTextHeight: CGFloat = 0
+            var itemWidth: CGFloat = 0
+
+            maxMediaTextWidth = maxWidth - self.messageLabelInsets(for: message).horizontal
+            if item.image == nil {
+                itemWidth = maxWidth
+                maxMediaTitleWidth = maxMediaTextWidth
+            } else {
+                itemWidth = item.size.width
+                // the media title/subtitle and subtitle is right to the badge view
+                // the max width of the title/subtitle depends on the available cell
+                // width minus fixed paddings and the badge size
+                maxMediaTitleWidth = maxWidth - FileView.badgeSize - (3 * FileMessageCell.insetHorizontalSmall)
+            }
+
+            var imageHeight = item.size.height
+            if maxWidth < item.size.width {
+                // Maintain the ratio if width is too great
+                imageHeight = maxWidth * item.size.height / item.size.width
+                itemWidth = maxWidth
+            }
+
+            var messageContainerSize = CGSize(width: itemWidth, height: imageHeight)
+            switch message.kind {
+            case .fileText(let mediaItem):
+                if let mediaTitle = mediaItem.text?[MediaItemConstants.mediaTitle] {
+                    mediaTitleHeight = mediaTitle.height(withConstrainedWidth: maxMediaTitleWidth)
+                    messageContainerSize.height += mediaTitleHeight
+                }
+                if let mediaSubtitle = mediaItem.text?[MediaItemConstants.mediaSubtitle] {
+                    mediaSubtitleHeight = mediaSubtitle.height(withConstrainedWidth: maxMediaTitleWidth)
+                    messageContainerSize.height += mediaSubtitleHeight
+                }
+
+                if let messageText = mediaItem.text?[MediaItemConstants.messageText], !messageText.string.isEmpty {
+                    messageTextHeight = messageText.height(withConstrainedWidth: maxMediaTextWidth)
+                    messageContainerSize.height += messageTextHeight
+                    messageContainerSize.height +=  self.messageLabelInsets(for: message).vertical
+                }
+
+            default:
+                fatalError("only fileText types can be calculated by FileMessageSizeCalculator")
+            }
+
+            return messageContainerSize
+        }
+
+        switch message.kind {
+        case .fileText(let item):
+
+            let maxImageWidth = item.image != nil ? messageContainerMaxWidth(for: message) : 280
+            return sizeForMediaItem(maxImageWidth, item)
+        default:
+            fatalError("messageContainerSize received unhandled MessageDataType: \(message.kind)")
+        }
+    }
+
+    open override func configure(attributes: UICollectionViewLayoutAttributes) {
+        super.configure(attributes: attributes)
+        guard let attributes = attributes as? MessagesCollectionViewLayoutAttributes else { return }
+
+        let dataSource = messagesLayout.messagesDataSource
+        let indexPath = attributes.indexPath
+        let message = dataSource.messageForItem(at: indexPath, in: messagesLayout.messagesCollectionView)
+
+        switch message.kind {
+        case .fileText:
+            attributes.messageLabelInsets = messageLabelInsets(for: message)
+            attributes.messageLabelFont = messageLabelFont
+        default:
+            break
+        }
+    }
+
+
+}

+ 5 - 1
deltachat-ios/MessageKit/Layout/MessagesCollectionViewFlowLayout.swift

@@ -167,6 +167,7 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
         return sizeCalculator
     }()
     lazy open var textMediaMessageSizeCalculator = TextMediaMessageSizeCalculator(layout: self)
+    lazy open var fileMessageSizeCalculator = FileMessageSizeCalculator(layout: self)
     lazy open var photoMessageSizeCalculator = MediaMessageSizeCalculator(layout: self)
     lazy open var videoMessageSizeCalculator = MediaMessageSizeCalculator(layout: self)
     lazy open var locationMessageSizeCalculator = LocationMessageSizeCalculator(layout: self)
@@ -194,8 +195,10 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
             return emojiMessageSizeCalculator
         case .photo:
             return photoMessageSizeCalculator
-        case .photoText, .videoText, .fileText:
+        case .photoText, .videoText:
             return textMediaMessageSizeCalculator
+        case .fileText:
+            return fileMessageSizeCalculator
         case .video:
             return videoMessageSizeCalculator
         case .location:
@@ -326,6 +329,7 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
         return [textMessageSizeCalculator,
                 attributedTextMessageSizeCalculator,
                 emojiMessageSizeCalculator,
+                fileMessageSizeCalculator,
                 textMediaMessageSizeCalculator,
                 photoMessageSizeCalculator,
                 videoMessageSizeCalculator,

+ 4 - 4
deltachat-ios/MessageKit/Layout/TextMediaMessageSizeCalculator.swift

@@ -60,8 +60,8 @@ open class TextMediaMessageSizeCalculator: MessageSizeCalculator {
 
             var messageContainerSize = CGSize(width: itemWidth, height: imageHeight)
             switch message.kind {
-            case .photoText(let mediaItem), .videoText(let mediaItem), .fileText(let mediaItem):
-                if let text = mediaItem.text {
+            case .photoText(let mediaItem), .videoText(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
@@ -73,7 +73,7 @@ open class TextMediaMessageSizeCalculator: MessageSizeCalculator {
         }
 
         switch message.kind {
-        case .photoText(let item), .videoText(let item), .fileText(let item):
+        case .photoText(let item), .videoText(let item):
             return sizeForMediaItem(maxImageWidth, item)
         default:
             fatalError("messageContainerSize received unhandled MessageDataType: \(message.kind)")
@@ -89,7 +89,7 @@ open class TextMediaMessageSizeCalculator: MessageSizeCalculator {
         let message = dataSource.messageForItem(at: indexPath, in: messagesLayout.messagesCollectionView)
 
         switch message.kind {
-        case .photoText, .videoText, .fileText:
+        case .photoText, .videoText:
             attributes.messageLabelInsets = messageLabelInsets(for: message)
             attributes.messageLabelFont = messageLabelFont
         default:

+ 6 - 1
deltachat-ios/MessageKit/Protocols/MediaItem.swift

@@ -40,6 +40,11 @@ public protocol MediaItem {
     /// The size of the media item.
     var size: CGSize { get }
 
-    var text: NSAttributedString? { get }
+    var text: [NSAttributedString]? { get }
+}
 
+struct MediaItemConstants {
+    static let messageText = 0
+    static let mediaTitle = 1
+    static let mediaSubtitle = 2
 }

+ 3 - 3
deltachat-ios/MessageKit/Views/Cells/AudioMessageCell.swift

@@ -72,9 +72,9 @@ open class AudioMessageCell: MessageContentCell {
         if let text = messageLabel.attributedText {
             let height = (text.height(withConstrainedWidth:
                 messageContainerView.frame.width -
-                    TextMediaMessageCell.insetHorizontalSmall -
-                    TextMediaMessageCell.insetHorizontalBig))
-            return height + TextMediaMessageCell.insetBottom + TextMediaMessageCell.insetTop
+                    AudioMessageCell.insetHorizontalSmall -
+                    AudioMessageCell.insetHorizontalBig))
+            return height + AudioMessageCell.insetBottom + AudioMessageCell.insetTop
         }
         return 0
     }

+ 136 - 0
deltachat-ios/MessageKit/Views/Cells/FileMessageCell.swift

@@ -0,0 +1,136 @@
+import UIKit
+
+// A subclass of `MessageContentCell` used to display mixed media messages.
+open class FileMessageCell: MessageContentCell {
+
+    public static let insetTop: CGFloat = 12
+    public static let insetBottom: CGFloat = 12
+    public static let insetHorizontalBig: CGFloat = 23
+    public static let insetHorizontalSmall: CGFloat = 12
+
+    private var mediaItem: MediaItem?
+
+    // MARK: - Properties
+    /// The `MessageCellDelegate` for the cell.
+    open override weak var delegate: MessageCellDelegate? {
+        didSet {
+            messageLabel.delegate = delegate
+        }
+    }
+
+    /// The label used to display the message's text.
+    open var messageLabel = MessageLabel()
+
+    lazy var fileView: FileView = {
+        let fileView = FileView()
+        fileView.translatesAutoresizingMaskIntoConstraints = false
+        return fileView
+    }()
+
+    // MARK: - Methods
+
+    /// Responsible for setting up the constraints of the cell's subviews.
+    open func setupConstraints(for messageKind: MessageKind) {
+        messageContainerView.removeConstraints(messageContainerView.constraints)
+        let fileViewHeight = messageContainerView.frame.height - getMessageLabelHeight()
+
+        let fileViewConstraints = [fileView.constraintHeightTo(fileViewHeight),
+                                    fileView.constraintAlignLeadingTo(messageContainerView),
+                                    fileView.constraintAlignTrailingTo(messageContainerView),
+                                    fileView.constraintAlignTopTo(messageContainerView),
+                                    ]
+        messageContainerView.addConstraints(fileViewConstraints)
+
+        messageLabel.frame = CGRect(x: 0,
+                                    y: messageContainerView.frame.height - getMessageLabelHeight(),
+                                    width: messageContainerView.frame.width,
+                                    height: getMessageLabelHeight())
+    }
+
+    func getMessageLabelHeight() -> CGFloat {
+        if let text = messageLabel.attributedText, !text.string.isEmpty {
+            let height = (text.height(withConstrainedWidth:
+                messageContainerView.frame.width -
+                    FileMessageCell.insetHorizontalSmall -
+                    FileMessageCell.insetHorizontalBig))
+            return height + FileMessageCell.insetBottom + FileMessageCell.insetTop
+        }
+        return 0
+    }
+
+    open override func setupSubviews() {
+        super.setupSubviews()
+        messageContainerView.addSubview(fileView)
+        messageContainerView.addSubview(messageLabel)
+    }
+
+    open override func prepareForReuse() {
+        super.prepareForReuse()
+        self.messageLabel.attributedText = nil
+        self.fileView.prepareForReuse()
+    }
+
+    open override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
+        super.apply(layoutAttributes)
+        if let attributes = layoutAttributes as? MessagesCollectionViewLayoutAttributes {
+            messageLabel.textInsets = attributes.messageLabelInsets
+            messageLabel.messageLabelFont = attributes.messageLabelFont
+        }
+    }
+
+    // MARK: - Configure Cell
+    open override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) {
+        super.configure(with: message, at: indexPath, and: messagesCollectionView)
+
+        guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else {
+            fatalError(MessageKitError.nilMessagesDisplayDelegate)
+        }
+
+        switch message.kind {
+        case .fileText(let mediaItem):
+            configureFileView(for: mediaItem)
+            configureMessageLabel(for: mediaItem,
+                                             with: displayDelegate,
+                                             message: message,
+                                             at: indexPath,
+                                             in: messagesCollectionView)
+
+        default:
+            fatalError("Unexpected message kind in FileMessageCell")
+        }
+
+        setupConstraints(for: message.kind)
+
+        //displayDelegate.configureMediaMessageImageView(imageView, for: message, at: indexPath, in: messagesCollectionView)
+    }
+
+
+    func configureFileView(for mediaItem: MediaItem) {
+        fileView.configureFor(mediaItem: mediaItem)
+    }
+
+    func configureMessageLabel(for mediaItem: MediaItem,
+                               with displayDelegate: MessagesDisplayDelegate,
+                               message: MessageType,
+                               at indexPath: IndexPath,
+                               in messagesCollectionView: MessagesCollectionView) {
+        let enabledDetectors = displayDelegate.enabledDetectors(for: message, at: indexPath, in: messagesCollectionView)
+        messageLabel.configure {
+           messageLabel.enabledDetectors = enabledDetectors
+           for detector in enabledDetectors {
+               let attributes = displayDelegate.detectorAttributes(for: detector, and: message, at: indexPath)
+               messageLabel.setAttributes(attributes, detector: detector)
+           }
+            messageLabel.attributedText = mediaItem.text?[MediaItemConstants.messageText]
+        }
+    }
+
+    /// Used to handle the cell's contentView's tap gesture.
+    /// Return false when the contentView does not need to handle the gesture.
+    open override func cellContentView(canHandle touchPoint: CGPoint) -> Bool {
+        let touchPointWithoutImageHeight = CGPoint(x: touchPoint.x,
+                                                   y: touchPoint.y - fileView.frame.height)
+        return messageLabel.handleGesture(touchPointWithoutImageHeight)
+    }
+
+}

+ 9 - 9
deltachat-ios/MessageKit/Views/Cells/TextMediaMessageCell.swift

@@ -34,11 +34,11 @@ open class TextMediaMessageCell: MessageContentCell {
         return playButtonView
     }()
 
-    open lazy var fileView: UIImageView = {
+ /*   open lazy var fileView: UIImageView = {
         let fileView = UIImageView(image: UIImage(named: "ic_attach_file_36pt"))
         fileView.translatesAutoresizingMaskIntoConstraints = false
         return fileView
-    }()
+    }()*/
 
     // MARK: - Methods
 
@@ -85,11 +85,11 @@ open class TextMediaMessageCell: MessageContentCell {
             let playButtonViewConstraints = [ playButtonView.constraintCenterXTo(imageView),
                                               playButtonView.constraintCenterYTo(imageView)]
             messageContainerView.addConstraints(playButtonViewConstraints)
-        case .fileText:
+        /*case .fileText:
             fileView.constraint(equalTo: CGSize(width: 35, height: 35))
             let fileViewConstraints = [ fileView.constraintCenterXTo(imageView),
                                                          fileView.constraintCenterYTo(imageView)]
-            messageContainerView.addConstraints(fileViewConstraints)
+            messageContainerView.addConstraints(fileViewConstraints)*/
         default:
             break
         }
@@ -106,7 +106,7 @@ open class TextMediaMessageCell: MessageContentCell {
         super.setupSubviews()
         messageContainerView.addSubview(imageView)
         messageContainerView.addSubview(playButtonView)
-        messageContainerView.addSubview(fileView)
+      //  messageContainerView.addSubview(fileView)
         messageContainerView.addSubview(messageLabel)
     }
 
@@ -131,7 +131,7 @@ open class TextMediaMessageCell: MessageContentCell {
         }
 
         configurePlayButtonView(for: message.kind)
-        configureFileView(for: message.kind)
+  //      configureFileView(for: message.kind)
         setupConstraints(for: message.kind)
 
         displayDelegate.configureMediaMessageImageView(imageView, for: message, at: indexPath, in: messagesCollectionView)
@@ -147,14 +147,14 @@ open class TextMediaMessageCell: MessageContentCell {
         }
     }
 
-    func configureFileView(for messageKind: MessageKind) {
+ /*   func configureFileView(for messageKind: MessageKind) {
         switch messageKind {
         case .fileText:
             fileView.isHidden = false
         default:
             fileView.isHidden = true
         }
-    }
+    }*/
 
     func configureImageView(for mediaItem: MediaItem) {
         imageView.image = mediaItem.image ?? mediaItem.placeholderImage
@@ -171,7 +171,7 @@ open class TextMediaMessageCell: MessageContentCell {
                let attributes = displayDelegate.detectorAttributes(for: detector, and: message, at: indexPath)
                messageLabel.setAttributes(attributes, detector: detector)
            }
-            messageLabel.attributedText = mediaItem.text
+            messageLabel.attributedText = mediaItem.text?[MediaItemConstants.messageText]
         }
     }
 

+ 124 - 0
deltachat-ios/MessageKit/Views/FileView.swift

@@ -0,0 +1,124 @@
+import UIKit
+import DcCore
+
+class FileView: UIView {
+
+    static let badgeSize: CGFloat = 54
+    let defaultHeight: CGFloat = 100
+    let defaultWidth: CGFloat = 250
+
+    private lazy var previewImageView: UIImageView = {
+        let imageView = UIImageView()
+        imageView.contentMode = .scaleAspectFill
+        imageView.translatesAutoresizingMaskIntoConstraints = false
+        return imageView
+    }()
+
+    private lazy var titleView: MessageLabel = {
+        let label = MessageLabel()
+        label.translatesAutoresizingMaskIntoConstraints = false
+        return label
+    }()
+
+    private lazy var subtitleView: MessageLabel = {
+        let label = MessageLabel()
+        label.translatesAutoresizingMaskIntoConstraints = false
+        return label
+    }()
+
+    private lazy var fileBadgeView: InitialsBadge = {
+        let defaultImage = UIImage(named: "ic_attach_file_36pt") ?? UIImage()
+        let badge: InitialsBadge = InitialsBadge(image: defaultImage, size: FileView.badgeSize)
+        badge.setColor(DcColors.middleGray)
+        badge.isAccessibilityElement = false
+        return badge
+    }()
+
+    private lazy var stackView: UIStackView = {
+        let stackView = UIStackView(arrangedSubviews: [titleView, subtitleView])
+        stackView.translatesAutoresizingMaskIntoConstraints = false
+        stackView.axis = .vertical
+        stackView.alignment = .leading
+        return stackView
+    }()
+
+    init() {
+        super.init(frame: CGRect(x: 0, y: 0, width: defaultWidth, height: defaultHeight))
+        translatesAutoresizingMaskIntoConstraints = false
+    }
+
+    func configureFor(mediaItem: MediaItem) {
+        removeConstraints(self.constraints)
+        previewImageView.removeFromSuperview()
+        //titleView.removeFromSuperview()
+        //subtitleView.removeFromSuperview()
+        fileBadgeView.removeFromSuperview()
+        setupSubviews(mediaItem: mediaItem)
+    }
+
+    private func setupSubviews(mediaItem: MediaItem) {
+        if let previewImage = mediaItem.image {
+            previewImageView.image = previewImage
+            addSubview(previewImageView)
+            addSubview(stackView)
+            //addSubview(titleView)
+            //addSubview(subtitleView)
+            addConstraints([
+                previewImageView.constraintAlignTopTo(self),
+                previewImageView.constraintAlignLeadingTo(self),
+                previewImageView.constraintAlignTrailingTo(self),
+                /*titleView.constraintAlignTopTo(previewImageView),
+                titleView.constraintAlignLeadingTo(self),
+                titleView.constraintAlignTrailingTo(self),
+                subtitleView.constraintAlignLeadingTo(self),
+                subtitleView.constraintAlignTrailingTo(self),
+                subtitleView.constraintAlignTopTo(titleView)*/
+            ])
+        } else {
+            fileBadgeView.setImage(mediaItem.placeholderImage)
+
+
+            addSubview(fileBadgeView)
+            addConstraints([
+                fileBadgeView.constraintAlignLeadingTo(self, paddingLeading: FileMessageCell.insetHorizontalSmall),
+                fileBadgeView.constraintWidthTo(FileView.badgeSize),
+                fileBadgeView.constraintHeightTo(FileView.badgeSize),
+                fileBadgeView.constraintCenterYTo(self)
+            ])
+            addSubview(stackView)
+            addConstraints([
+                stackView.constraintCenterYTo(self),
+                stackView.constraintToTrailingOf(fileBadgeView, paddingLeading: FileMessageCell.insetHorizontalSmall),
+                stackView.constraintAlignTrailingTo(self, paddingTrailing: FileMessageCell.insetHorizontalSmall)
+            ])
+            /*addSubview(titleView)
+            addConstraints([
+                titleView.constraintAlignTopTo(self, paddingTop: FileMessageCell.insetTop),
+                titleView.constraintToTrailingOf(fileBadgeView, paddingLeading: FileMessageCell.insetHorizontalSmall),
+                titleView.constraintAlignTrailingTo(self, paddingTrailing: FileMessageCell.insetHorizontalSmall),
+            ])
+            addSubview(subtitleView)
+            addConstraints([
+                subtitleView.constraintToTrailingOf(fileBadgeView, paddingLeading: FileMessageCell.insetHorizontalSmall),
+                subtitleView.constraintAlignTrailingTo(self, paddingTrailing: FileMessageCell.insetHorizontalSmall),
+                subtitleView.constraintToBottomOf(titleView)
+            ])*/
+        }
+        if let title = mediaItem.text?[MediaItemConstants.mediaTitle] {
+            titleView.attributedText = title
+        }
+        if let subtitle = mediaItem.text?[MediaItemConstants.mediaSubtitle] {
+            subtitleView.attributedText = subtitle
+        }
+    }
+
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    func prepareForReuse() {
+        titleView.attributedText = nil
+        subtitleView.attributedText = nil
+        previewImageView.image = nil
+    }
+}

+ 1 - 0
deltachat-ios/MessageKit/Views/MessagesCollectionView.swift

@@ -76,6 +76,7 @@ open class MessagesCollectionView: UICollectionView {
         register(TextMessageCell.self)
         register(MediaMessageCell.self)
         register(TextMediaMessageCell.self)
+        register(FileMessageCell.self)
         register(LocationMessageCell.self)
         register(AudioMessageCell.self)
         register(ContactMessageCell.self)

+ 5 - 2
deltachat-ios/Model/Media.swift

@@ -8,7 +8,7 @@ struct Media: MediaItem {
     var image: UIImage?
 
     var placeholderImage: UIImage = UIImage(color: .gray, size: CGSize(width: 250, height: 100))!
-    var text: NSAttributedString?
+    var text: [NSAttributedString]?
 
     var size: CGSize {
         if let image = image {
@@ -18,9 +18,12 @@ struct Media: MediaItem {
         }
     }
 
-    init(url: URL? = nil, image: UIImage? = nil, text: NSAttributedString? = nil) {
+    init(url: URL? = nil, image: UIImage? = nil, placeholderImage: UIImage? = nil, text: [NSAttributedString]? = nil) {
         self.url = url
         self.image = image
         self.text = text
+        if let placeholderImage = placeholderImage {
+            self.placeholderImage = placeholderImage
+        }
     }
 }