mirror of
https://github.com/MessageKit/MessageKit.git
synced 2026-02-06 19:03:19 +00:00
Merge remote-tracking branch 'origin/3.0.0-beta' into typingIndicatorView
This commit is contained in:
@@ -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
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user