From 08451fbde04e4ef4faa4cd77eb1e70eec43b4b6f Mon Sep 17 00:00:00 2001 From: Iosif Moldovan Date: Tue, 12 Mar 2019 16:02:52 +0200 Subject: [PATCH] Add contact share message support --- .../Images/disclouser.png | Bin 0 -> 165 bytes .../Images/disclouser@2x.png | Bin 0 -> 244 bytes .../Images/disclouser@3x.png | Bin 0 -> 386 bytes .../Sources/Data Generation/SampleData.swift | 41 ++++- Example/Sources/Models/MockMessage.swift | 19 +++ .../SettingsViewController.swift | 3 +- .../Controllers/MessagesViewController.swift | 4 + Sources/Extensions/UIImage+Extension.swift | 43 +++++ .../Layout/ContactMessageSizeCalculator.swift | 73 +++++++++ .../MessagesCollectionViewFlowLayout.swift | 6 +- Sources/Models/MessageKind.swift | 3 + Sources/Protocols/ContactItem.swift | 84 ++++++++++ Sources/Views/Cells/AudioMessageCell.swift | 17 +- Sources/Views/Cells/ContactMessageCell.swift | 150 ++++++++++++++++++ Sources/Views/MessagesCollectionView.swift | 1 + 15 files changed, 422 insertions(+), 22 deletions(-) create mode 100644 Assets/MessageKitAssets.bundle/Images/disclouser.png create mode 100644 Assets/MessageKitAssets.bundle/Images/disclouser@2x.png create mode 100644 Assets/MessageKitAssets.bundle/Images/disclouser@3x.png create mode 100644 Sources/Extensions/UIImage+Extension.swift create mode 100644 Sources/Layout/ContactMessageSizeCalculator.swift create mode 100644 Sources/Protocols/ContactItem.swift create mode 100644 Sources/Views/Cells/ContactMessageCell.swift diff --git a/Assets/MessageKitAssets.bundle/Images/disclouser.png b/Assets/MessageKitAssets.bundle/Images/disclouser.png new file mode 100644 index 0000000000000000000000000000000000000000..1c5a8c9ed04736f3df9ad136e7b2511d4c22349e GIT binary patch literal 165 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|;yqm)Lp*qs z6C_v{D>|$f$Y6+KIb_0k%OSX)`D4989z)Ws27?7FOEx~?zo0bhmx@fr!3UdKI@Ju_ z8W)=IDg}5giu`f;NpWc?Lo!(3 zPS53PP!MogYW%fu%fENMtA(%VJmKt0dBdyYmT=ZacdG~sBZmV6lRyIli^2!7#tY4x zbVQ44?@hfP7WH)7to6ATsuHPX#r%d}g_7PUs=RSNBgN{(;Z$5|DAa9m%YQjf;fhyE z;kPgBb(fO+`p+}{wPwm+!RXoNYbU;)fBfE_x7%+0@=3_Gn{NDDIkk=BYIbx>O#*jo j+^kjw2DrU;3I&XR78lMEyV-sL=yC>6S3j3^P6t7l&*#SpJ01uzpR?_&nG@5p9F4!%ewWFr$NqJ`dH6`}?X45^{;grjOcrg} z!uaH_g6IT2sm~$DssrMqA6f+SGp;n)tI!xCpuw{<>x}o!`|XeRiS`+P6_~(vnLCK_ z{a#rm;S|T?t=k1b q&R1Cv^381ZMl8Msg@BLB2i9~R=BrDm{JI4UR0dC1KbLh*2~7Yx%arH< literal 0 HcmV?d00001 diff --git a/Example/Sources/Data Generation/SampleData.swift b/Example/Sources/Data Generation/SampleData.swift index 9f666bbf..67291de4 100644 --- a/Example/Sources/Data Generation/SampleData.swift +++ b/Example/Sources/Data Generation/SampleData.swift @@ -43,12 +43,11 @@ final internal class SampleData { case Url = 7 case Phone = 8 case Custom = 9 + case ShareContact = 10 static func random() -> MessageTypes { - // Update as new enumerations are added - let maxValue = Custom.rawValue - - let rand = arc4random_uniform(maxValue+1) + let maxValue = UInt32(MessageTypes.allCases.count) + let rand = arc4random_uniform(maxValue) return MessageTypes(rawValue: rand)! } } @@ -59,6 +58,15 @@ final internal class SampleData { let wu = Sender(id: "000003", displayName: "Wu Zhong") lazy var senders = [nathan, steven, wu] + + lazy var contactsToShare = [ + MockContactItem(firstName: "System"), + MockContactItem(firstName: "Nathan", lastName: "Tannar", emails: ["test@test.com"]), + MockContactItem(firstName: "Steven", lastName: "Deutsch", phoneNumbers: ["+1-202-555-0114", "+1-202-555-0145"]), + MockContactItem(firstName: "Wu", lastName: "Zhong", phoneNumbers: ["202-555-0158"]), + MockContactItem(phoneNumbers: ["+40 123 123"]), + MockContactItem(emails: ["test@test.com"]) + ] var currentSender: Sender { return nathan @@ -142,7 +150,7 @@ final internal class SampleData { func randomMessageType() -> MessageTypes { let messageType = MessageTypes.random() - + if !UserDefaults.standard.bool(forKey: "\(messageType)" + " Messages") { return randomMessageType() } @@ -150,6 +158,7 @@ final internal class SampleData { return messageType } + // swiftlint:disable cyclomatic_complexity func randomMessage(allowedSenders: [Sender]) -> MockMessage { let randomNumberSender = Int(arc4random_uniform(UInt32(allowedSenders.count))) @@ -190,10 +199,32 @@ final internal class SampleData { return MockMessage(text: "123-456-7890", sender: sender, messageId: uniqueID, date: date) case .Custom: return MockMessage(custom: "Someone left the conversation", sender: system, messageId: uniqueID, date: date) + case .ShareContact: + let randomContact = Int(arc4random_uniform(UInt32(contactsToShare.count))) + return MockMessage(contact: contactsToShare[randomContact], sender: sender, messageId: uniqueID, date: date) } } + // swiftlint:enable cyclomatic_complexity func getMessages(count: Int, completion: ([MockMessage]) -> Void) { +// +// let firstContact = MockContactItem(firstName: "Quite a long name", lastName: "For testing purpose", phoneNumbers: ["0754324"]) +// let secondContact = MockContactItem(firstName: "Steven", lastName: "Deutsch", emails: ["test@test.com"]) +// let thirdContact = MockContactItem(phoneNumbers: ["+40 123 123"]) +// let fourthContact = MockContactItem(emails: ["test@test.com"]) +// +// let contacts = [firstContact, secondContact, thirdContact, fourthContact] +// var messages = [MockMessage]() +// for contact in contacts { +// let randomNumberSender = Int(arc4random_uniform(UInt32(senders.count))) +// let sender = senders[randomNumberSender] +// let date = dateAddingRandomTime() +// let uniqueID = NSUUID().uuidString +// let message = MockMessage(contact: contact, sender: sender, messageId: uniqueID, date: date) +// messages.append(message) +// } +// completion(messages) + var messages: [MockMessage] = [] // Disable Custom Messages UserDefaults.standard.set(false, forKey: "Custom Messages") diff --git a/Example/Sources/Models/MockMessage.swift b/Example/Sources/Models/MockMessage.swift index c730d33b..dfb53507 100644 --- a/Example/Sources/Models/MockMessage.swift +++ b/Example/Sources/Models/MockMessage.swift @@ -70,6 +70,22 @@ private struct MockAudiotem: AudioItem { } +struct MockContactItem: ContactItem { + + var firstName: String? + var lastName: String? + var phoneNumbers: [String] + var emails: [String] + + init(firstName: String? = nil, lastName: String? = nil, phoneNumbers: [String] = [], emails: [String] = []) { + self.firstName = firstName + self.lastName = lastName + self.phoneNumbers = phoneNumbers + self.emails = emails + } + +} + internal struct MockMessage: MessageType { var messageId: String @@ -120,4 +136,7 @@ internal struct MockMessage: MessageType { self.init(kind: .audio(audioItem), sender: sender, messageId: messageId, date: date) } + init(contact: MockContactItem, sender: Sender, messageId: String, date: Date) { + self.init(kind: .contact(contact), sender: sender, messageId: messageId, date: date) + } } diff --git a/Example/Sources/View Controllers/SettingsViewController.swift b/Example/Sources/View Controllers/SettingsViewController.swift index 6aa88ccb..1830eb15 100644 --- a/Example/Sources/View Controllers/SettingsViewController.swift +++ b/Example/Sources/View Controllers/SettingsViewController.swift @@ -34,7 +34,7 @@ final internal class SettingsViewController: UITableViewController { return .lightContent } - let cells = ["Mock messages count", "Text Messages", "AttributedText Messages", "Photo Messages", "Video Messages", "Audio Messages", "Emoji Messages", "Location Messages", "Url Messages", "Phone Messages"] + let cells = ["Mock messages count", "Text Messages", "AttributedText Messages", "Photo Messages", "Video Messages", "Audio Messages", "Emoji Messages", "Location Messages", "Url Messages", "Phone Messages", "ShareContact Messages"] // MARK: - Picker @@ -138,6 +138,7 @@ final internal class SettingsViewController: UITableViewController { let cell = cells[sender.tag] UserDefaults.standard.set(sender.isOn, forKey: cell) + UserDefaults.standard.synchronize() } } diff --git a/Sources/Controllers/MessagesViewController.swift b/Sources/Controllers/MessagesViewController.swift index 3d36c606..9b0e1247 100644 --- a/Sources/Controllers/MessagesViewController.swift +++ b/Sources/Controllers/MessagesViewController.swift @@ -212,6 +212,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) } diff --git a/Sources/Extensions/UIImage+Extension.swift b/Sources/Extensions/UIImage+Extension.swift new file mode 100644 index 00000000..a56fa9d0 --- /dev/null +++ b/Sources/Extensions/UIImage+Extension.swift @@ -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 ImageTye: 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: ImageTye) -> UIImage? { + let assetBundle = Bundle.messageKitAssetBundle() + let imagePath = assetBundle.path(forResource: type.rawValue, ofType: "png", inDirectory: "Images") + let image = UIImage(contentsOfFile: imagePath ?? "") + return image + } + +} diff --git a/Sources/Layout/ContactMessageSizeCalculator.swift b/Sources/Layout/ContactMessageSizeCalculator.swift new file mode 100644 index 00000000..14d36fa4 --- /dev/null +++ b/Sources/Layout/ContactMessageSizeCalculator.swift @@ -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.getName(), 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 + } + +} diff --git a/Sources/Layout/MessagesCollectionViewFlowLayout.swift b/Sources/Layout/MessagesCollectionViewFlowLayout.swift index 010a2686..e73a5c6d 100644 --- a/Sources/Layout/MessagesCollectionViewFlowLayout.swift +++ b/Sources/Layout/MessagesCollectionViewFlowLayout.swift @@ -147,6 +147,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) /// - Note: /// If you override this method, remember to call MessageLayoutDelegate's customCellSizeCalculator(for:at:in:) method for MessageKind.custom messages, if necessary @@ -167,6 +168,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) } @@ -275,7 +278,8 @@ open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout { photoMessageSizeCalculator, videoMessageSizeCalculator, locationMessageSizeCalculator, - audioMessageSizeCalculator + audioMessageSizeCalculator, + contactMessageSizeCalculator ] } diff --git a/Sources/Models/MessageKind.swift b/Sources/Models/MessageKind.swift index 0d351dce..2b8d46f2 100644 --- a/Sources/Models/MessageKind.swift +++ b/Sources/Models/MessageKind.swift @@ -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: diff --git a/Sources/Protocols/ContactItem.swift b/Sources/Protocols/ContactItem.swift new file mode 100644 index 00000000..59c36942 --- /dev/null +++ b/Sources/Protocols/ContactItem.swift @@ -0,0 +1,84 @@ +/* + 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 first name + var firstName: String? { get } + + /// contact last name + var lastName: String? { get } + + /// contact phone numbers + var phoneNumbers: [String] { get } + + /// contact emails + var emails: [String] { get } + + /// Return full name from given contact item + /// By default the priority in computing contact name is: + /// 1. first + last name + /// 2. phone number + /// 3. email address + func getName() -> String + + /// Return name initials from given contact. If first and last name is not set it returns # + func getInitials() -> String +} + +/// Default Implementation for contact name and initials +public extension ContactItem { + + func getName() -> String { + var name = firstName ?? "" + if let lastName = lastName, lastName.count > 0 { + name += (name.count > 0) ? " \(lastName)" : lastName + } + if name.count == 0 { // if name is still 0 show first phone number + name = phoneNumbers.first ?? "" + } + if name.count == 0 { // if name is still 0 show first email + name = emails.first ?? "" + } + return name + } + + func getInitials() -> String { + var initials = getFirstLeterFrom(firstName) + getFirstLeterFrom(lastName) + if initials.count == 0 { + initials = "#" + } + return initials + } + + private func getFirstLeterFrom(_ string: String?) -> String { + guard let value = string, value.count > 0 else { + return "" + } + return String(value.first!).capitalized + } +} diff --git a/Sources/Views/Cells/AudioMessageCell.swift b/Sources/Views/Cells/AudioMessageCell.swift index 3050ebfc..4c32567d 100644 --- a/Sources/Views/Cells/AudioMessageCell.swift +++ b/Sources/Views/Cells/AudioMessageCell.swift @@ -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 od default iamges used to decorate play button - public enum ImageName: String { - case play - case pause - } - /// The play button view to display on audio messages. open 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) diff --git a/Sources/Views/Cells/ContactMessageCell.swift b/Sources/Views/Cells/ContactMessageCell.swift new file mode 100644 index 00000000..01e5594d --- /dev/null +++ b/Sources/Views/Cells/ContactMessageCell.swift @@ -0,0 +1,150 @@ +/* + 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 ConstrainsID: String { + case initialsContainerLeftConstraint + case disclouserRigtConstraint + } + + /// The view container that holds contact initials + open lazy var initialsContainerView: UIView = { + let initialsContainer = UIView(frame: CGRect.zero) + initialsContainer.backgroundColor = .white + initialsContainer.layer.shadowColor = UIColor.gray.cgColor + initialsContainer.layer.shadowOffset = CGSize(width: 0, height: 2) + initialsContainer.layer.shadowRadius = 2 + initialsContainer.layer.shadowOpacity = 0.6 + + return initialsContainer + }() + + /// The label that display the contact initials + open 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 + open lazy var nameLabel: UILabel = { + let nameLabel = UILabel(frame: CGRect.zero) + nameLabel.numberOfLines = 0 + + return nameLabel + }() + + /// The disclouser image view + open lazy var disclouserImageView: 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) + if let attributes = layoutAttributes as? MessagesCollectionViewLayoutAttributes { + nameLabel.font = attributes.messageLabelFont + } + } + + open override func setupSubviews() { + super.setupSubviews() + messageContainerView.addSubview(initialsContainerView) + messageContainerView.addSubview(nameLabel) + messageContainerView.addSubview(disclouserImageView) + 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 = ConstrainsID.initialsContainerLeftConstraint.rawValue + initialsContainerView.layer.cornerRadius = 13 + initialsLabel.fillSuperview() + disclouserImageView.constraint(equalTo: CGSize(width: 20, height: 20)) + let disclouserConstraints = disclouserImageView.addConstraints(right: messageContainerView.rightAnchor, centerY: messageContainerView.centerYAnchor, + rightConstant: -10) + disclouserConstraints.first?.identifier = ConstrainsID.disclouserRigtConstraint.rawValue + nameLabel.addConstraints(messageContainerView.topAnchor, + left: initialsContainerView.rightAnchor, + bottom: messageContainerView.bottomAnchor, + right: disclouserImageView.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.getName() + initialsLabel.text = contactItem.getInitials() + // setup constraints + guard let dataSource = messagesCollectionView.messagesDataSource else { + fatalError(MessageKitError.nilMessagesDataSource) + } + let initialsContainerLeftConstraint = messageContainerView.constraints.filter { (constraint) -> Bool in + return constraint.identifier == ConstrainsID.initialsContainerLeftConstraint.rawValue + }.first + let disclouserRightConstraint = messageContainerView.constraints.filter { (constraint) -> Bool in + return constraint.identifier == ConstrainsID.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 + disclouserImageView.tintColor = textColor + } + +} diff --git a/Sources/Views/MessagesCollectionView.swift b/Sources/Views/MessagesCollectionView.swift index da759b41..54020ce2 100644 --- a/Sources/Views/MessagesCollectionView.swift +++ b/Sources/Views/MessagesCollectionView.swift @@ -66,6 +66,7 @@ open class MessagesCollectionView: UICollectionView { register(MediaMessageCell.self) register(LocationMessageCell.self) register(AudioMessageCell.self) + register(ContactMessageCell.self) register(MessageReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader) register(MessageReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter) }