Merge remote-tracking branch 'origin/3.0.0-beta' into typingIndicatorView

This commit is contained in:
Nathan Tannar
2019-04-05 00:11:14 -07:00
19 changed files with 376 additions and 24 deletions
@@ -289,6 +289,10 @@ UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
let cell = messagesCollectionView.dequeueReusableCell(AudioMessageCell.self, for: indexPath)
cell.configure(with: message, at: indexPath, and: messagesCollectionView)
return cell
case .contact:
let cell = messagesCollectionView.dequeueReusableCell(ContactMessageCell.self, for: indexPath)
cell.configure(with: message, at: indexPath, and: messagesCollectionView)
return cell
case .custom:
return messagesDataSource.customCell(for: message, at: indexPath, in: messagesCollectionView)
}
@@ -0,0 +1,43 @@
/*
MIT License
Copyright (c) 2017-2018 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.
*/
public enum ImageType: String {
case play
case pause
case disclouser
}
import UIKit
/// This extension provide a way to access image resources with in framework
public extension UIImage {
public class func messageKitImageWith(type: ImageType) -> UIImage? {
let assetBundle = Bundle.messageKitAssetBundle()
let imagePath = assetBundle.path(forResource: type.rawValue, ofType: "png", inDirectory: "Images")
let image = UIImage(contentsOfFile: imagePath ?? "")
return image
}
}
@@ -0,0 +1,73 @@
/*
MIT License
Copyright (c) 2017-2018 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 Foundation
open class ContactMessageSizeCalculator: MessageSizeCalculator {
public var incomingMessageNameLabelInsets = UIEdgeInsets(top: 7, left: 46, bottom: 7, right: 30)
public var outgoingMessageNameLabelInsets = UIEdgeInsets(top: 7, left: 41, bottom: 7, right: 35)
public var contactLabelFont = UIFont.preferredFont(forTextStyle: .body)
internal func contactLabelInsets(for message: MessageType) -> UIEdgeInsets {
let dataSource = messagesLayout.messagesDataSource
let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
return isFromCurrentSender ? outgoingMessageNameLabelInsets : incomingMessageNameLabelInsets
}
open override func messageContainerMaxWidth(for message: MessageType) -> CGFloat {
let maxWidth = super.messageContainerMaxWidth(for: message)
let textInsets = contactLabelInsets(for: message)
return maxWidth - textInsets.horizontal
}
open override func messageContainerSize(for message: MessageType) -> CGSize {
let maxWidth = messageContainerMaxWidth(for: message)
var messageContainerSize: CGSize
let attributedText: NSAttributedString
switch message.kind {
case .contact(let item):
attributedText = NSAttributedString(string: item.displayName, attributes: [.font: contactLabelFont])
default:
fatalError("messageContainerSize received unhandled MessageDataType: \(message.kind)")
}
messageContainerSize = labelSize(for: attributedText, considering: maxWidth)
let messageInsets = contactLabelInsets(for: message)
messageContainerSize.width += messageInsets.horizontal
messageContainerSize.height += messageInsets.vertical
return messageContainerSize
}
open override func configure(attributes: UICollectionViewLayoutAttributes) {
super.configure(attributes: attributes)
guard let attributes = attributes as? MessagesCollectionViewLayoutAttributes else { return }
attributes.messageLabelFont = contactLabelFont
}
}
@@ -167,6 +167,7 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
lazy open var videoMessageSizeCalculator = MediaMessageSizeCalculator(layout: self)
lazy open var locationMessageSizeCalculator = LocationMessageSizeCalculator(layout: self)
lazy open var audioMessageSizeCalculator = AudioMessageSizeCalculator(layout: self)
lazy open var contactMessageSizeCalculator = ContactMessageSizeCalculator(layout: self)
lazy open var typingIndicatorSizeCalculator = TypingCellSizeCalculator(layout: self)
/// Note:
@@ -194,6 +195,8 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
return locationMessageSizeCalculator
case .audio:
return audioMessageSizeCalculator
case .contact:
return contactMessageSizeCalculator
case .custom:
return messagesLayoutDelegate.customCellSizeCalculator(for: message, at: indexPath, in: messagesCollectionView)
}
@@ -317,7 +320,8 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
photoMessageSizeCalculator,
videoMessageSizeCalculator,
locationMessageSizeCalculator,
audioMessageSizeCalculator
audioMessageSizeCalculator,
contactMessageSizeCalculator
]
}
+3
View File
@@ -53,6 +53,9 @@ public enum MessageKind {
/// An audio message.
case audio(AudioItem)
/// A contact message.
case contact(ContactItem)
/// A custom message.
/// - Note: Using this case requires that you implement the following methods and handle this case:
+41
View File
@@ -0,0 +1,41 @@
/*
MIT License
Copyright (c) 2017-2018 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 Foundation
/// A protocol used to represent the data for a contact message.
public protocol ContactItem {
/// contact displayed name
var displayName: String { get }
/// initials from contact first and last name
var initials: String { get }
/// contact phone numbers
var phoneNumbers: [String] { get }
/// contact emails
var emails: [String] { get }
}
+2 -15
View File
@@ -28,17 +28,11 @@ import AVFoundation
/// A subclass of `MessageContentCell` used to display video and audio messages.
open class AudioMessageCell: MessageContentCell {
/// The `ImageName` enum holds the names of default iamges used to decorate play button
public enum ImageName: String {
case play
case pause
}
/// The play button view to display on audio messages.
public lazy var playButton: UIButton = {
let playButton = UIButton(type: .custom)
let playImage = AudioMessageCell.getImageWithName(.play)
let pauseImage = AudioMessageCell.getImageWithName(.pause)
let playImage = UIImage.messageKitImageWith(type: .play)
let pauseImage = UIImage.messageKitImageWith(type: .pause)
playButton.setImage(playImage?.withRenderingMode(.alwaysTemplate), for: .normal)
playButton.setImage(pauseImage?.withRenderingMode(.alwaysTemplate), for: .selected)
return playButton
@@ -84,13 +78,6 @@ open class AudioMessageCell: MessageContentCell {
durationLabel.text = "0:00"
}
open class func getImageWithName(_ imageName: ImageName) -> UIImage? {
let assetBundle = Bundle.messageKitAssetBundle()
let imagePath = assetBundle.path(forResource: imageName.rawValue, ofType: "png", inDirectory: "Images")
let image = UIImage(contentsOfFile: imagePath ?? "")
return image
}
/// Handle tap gesture on contentView and its subviews.
open override func handleTapGesture(_ gesture: UIGestureRecognizer) {
let touchLocation = gesture.location(in: self)
@@ -0,0 +1,142 @@
/*
MIT License
Copyright (c) 2017-2018 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 ContactMessageCell: MessageContentCell {
public enum ConstraintsID: String {
case initialsContainerLeftConstraint
case disclouserRigtConstraint
}
/// The view container that holds contact initials
public lazy var initialsContainerView: UIView = {
let initialsContainer = UIView(frame: CGRect.zero)
initialsContainer.backgroundColor = .white
return initialsContainer
}()
/// The label that display the contact initials
public lazy var initialsLabel: UILabel = {
let initialsLabel = UILabel(frame: CGRect.zero)
initialsLabel.textAlignment = .center
initialsLabel.textColor = .darkText
initialsLabel.font = UIFont.preferredFont(forTextStyle: .footnote)
return initialsLabel
}()
/// The label that display contact name
public lazy var nameLabel: UILabel = {
let nameLabel = UILabel(frame: CGRect.zero)
nameLabel.numberOfLines = 0
return nameLabel
}()
/// The disclouser image view
public lazy var disclosureImageView: UIImageView = {
let disclouserImage = UIImage.messageKitImageWith(type: .disclouser)?.withRenderingMode(.alwaysTemplate)
let disclouser = UIImageView(image: disclouserImage)
return disclouser
}()
// MARK: - Methods
open override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
guard let attributes = layoutAttributes as? MessagesCollectionViewLayoutAttributes else {
return
}
nameLabel.font = attributes.messageLabelFont
}
open override func setupSubviews() {
super.setupSubviews()
messageContainerView.addSubview(initialsContainerView)
messageContainerView.addSubview(nameLabel)
messageContainerView.addSubview(disclosureImageView)
initialsContainerView.addSubview(initialsLabel)
setupConstraints()
}
open override func prepareForReuse() {
super.prepareForReuse()
nameLabel.text = ""
initialsLabel.text = ""
}
open func setupConstraints() {
initialsContainerView.constraint(equalTo: CGSize(width: 26, height: 26))
let initialsConstraints = initialsContainerView.addConstraints(left: messageContainerView.leftAnchor, centerY: messageContainerView.centerYAnchor,
leftConstant: 5)
initialsConstraints.first?.identifier = ConstraintsID.initialsContainerLeftConstraint.rawValue
initialsContainerView.layer.cornerRadius = 13
initialsLabel.fillSuperview()
disclosureImageView.constraint(equalTo: CGSize(width: 20, height: 20))
let disclosureConstraints = disclosureImageView.addConstraints(right: messageContainerView.rightAnchor, centerY: messageContainerView.centerYAnchor,
rightConstant: -10)
disclosureConstraints.first?.identifier = ConstraintsID.disclouserRigtConstraint.rawValue
nameLabel.addConstraints(messageContainerView.topAnchor,
left: initialsContainerView.rightAnchor,
bottom: messageContainerView.bottomAnchor,
right: disclosureImageView.leftAnchor,
topConstant: 0,
leftConstant: 10,
bottomConstant: 0,
rightConstant: 5)
}
// MARK: - Configure Cell
open override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) {
super.configure(with: message, at: indexPath, and: messagesCollectionView)
// setup data
guard case let .contact(contactItem) = message.kind else { fatalError("Failed decorate audio cell") }
nameLabel.text = contactItem.displayName
initialsLabel.text = contactItem.initials
// setup constraints
guard let dataSource = messagesCollectionView.messagesDataSource else {
fatalError(MessageKitError.nilMessagesDataSource)
}
let initialsContainerLeftConstraint = messageContainerView.constraints.filter { (constraint) -> Bool in
return constraint.identifier == ConstraintsID.initialsContainerLeftConstraint.rawValue
}.first
let disclouserRightConstraint = messageContainerView.constraints.filter { (constraint) -> Bool in
return constraint.identifier == ConstraintsID.disclouserRigtConstraint.rawValue
}.first
if dataSource.isFromCurrentSender(message: message) { // outgoing message
initialsContainerLeftConstraint?.constant = 5
disclouserRightConstraint?.constant = -10
} else { // incoming message
initialsContainerLeftConstraint?.constant = 10
disclouserRightConstraint?.constant = -5
}
// setup colors
guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else {
fatalError(MessageKitError.nilMessagesDisplayDelegate)
}
let textColor = displayDelegate.textColor(for: message, at: indexPath, in: messagesCollectionView)
nameLabel.textColor = textColor
disclosureImageView.tintColor = textColor
}
}
@@ -77,6 +77,7 @@ open class MessagesCollectionView: UICollectionView {
register(MediaMessageCell.self)
register(LocationMessageCell.self)
register(AudioMessageCell.self)
register(ContactMessageCell.self)
register(TypingIndicatorCell.self)
register(MessageReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader)
register(MessageReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter)