From 08451fbde04e4ef4faa4cd77eb1e70eec43b4b6f Mon Sep 17 00:00:00 2001 From: Iosif Moldovan Date: Tue, 12 Mar 2019 16:02:52 +0200 Subject: [PATCH 1/5] 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) } From ca14e17a4016184f40c6c929924aca834610cb54 Mon Sep 17 00:00:00 2001 From: Iosif Moldovan Date: Tue, 12 Mar 2019 16:54:08 +0200 Subject: [PATCH 2/5] add missing references from message kit framework --- MessageKit.xcodeproj/project.pbxproj | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj index 6ed27369..9101462c 100644 --- a/MessageKit.xcodeproj/project.pbxproj +++ b/MessageKit.xcodeproj/project.pbxproj @@ -35,16 +35,19 @@ 1FF377AA20087D78004FD648 /* MessagesViewController+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF377A920087D78004FD648 /* MessagesViewController+Menu.swift */; }; 1FF377AC20087DA2004FD648 /* MessagesViewController+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF377AB20087DA2004FD648 /* MessagesViewController+Keyboard.swift */; }; 382C794221705D2000F4FAF5 /* HorizontalEdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 382C794121705D2000F4FAF5 /* HorizontalEdgeInsets.swift */; }; - 4C508649221C0BBA0043943C /* AccessoryPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C508648221C0BBA0043943C /* AccessoryPosition.swift */; }; 383B9EB121728BAD008AB91A /* SenderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 383B9EB021728BAD008AB91A /* SenderType.swift */; }; 38A2230F221FB8A300D14DAF /* MessageInputBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A2230E221FB8A300D14DAF /* MessageInputBar.swift */; }; 38A223112223493500D14DAF /* InputBarAccessoryView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 38A223102223493400D14DAF /* InputBarAccessoryView.framework */; }; - 38C2AE7C20D4878D00F8079E /* MessageInputBar.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 38C2AE7B20D4878D00F8079E /* MessageInputBar.framework */; }; 38F8062F2173CD8F00CDB9DB /* MockUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38F8062D2173CD4300CDB9DB /* MockUser.swift */; }; + 4C508649221C0BBA0043943C /* AccessoryPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C508648221C0BBA0043943C /* AccessoryPosition.swift */; }; 5073C1152175BE750040EAD5 /* AudioItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5073C1142175BE750040EAD5 /* AudioItem.swift */; }; 5073C1192175BE960040EAD5 /* AudioMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5073C1182175BE950040EAD5 /* AudioMessageCell.swift */; }; 5073C11D2175BEC60040EAD5 /* AudioMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5073C11C2175BEC60040EAD5 /* AudioMessageSizeCalculator.swift */; }; 5073C1232175C1980040EAD5 /* sound1.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 5073C1222175C1980040EAD5 /* sound1.m4a */; }; + 50FF34552237FE4C0004DCD7 /* ContactItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50FF34542237FE4C0004DCD7 /* ContactItem.swift */; }; + 50FF34572237FE6A0004DCD7 /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50FF34562237FE6A0004DCD7 /* UIImage+Extension.swift */; }; + 50FF34592237FE850004DCD7 /* ContactMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50FF34582237FE840004DCD7 /* ContactMessageCell.swift */; }; + 50FF345B2237FE9C0004DCD7 /* ContactMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50FF345A2237FE9C0004DCD7 /* ContactMessageSizeCalculator.swift */; }; 88916B2D1CF0DF2F00469F91 /* MessageKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88916B221CF0DF2F00469F91 /* MessageKit.framework */; }; 8962AC8A1F87AB7D0030B058 /* MessagesCollectionViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8962AC831F87AB230030B058 /* MessagesCollectionViewTests.swift */; }; 8962AC8C1F87AB7D0030B058 /* AvatarViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8962AC851F87AB230030B058 /* AvatarViewTests.swift */; }; @@ -143,12 +146,16 @@ 38A2230E221FB8A300D14DAF /* MessageInputBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageInputBar.swift; sourceTree = ""; }; 38A223102223493400D14DAF /* InputBarAccessoryView.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = InputBarAccessoryView.framework; path = Carthage/Build/iOS/InputBarAccessoryView.framework; sourceTree = ""; }; 38C2AE7B20D4878D00F8079E /* MessageInputBar.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageInputBar.framework; path = Carthage/Build/iOS/MessageInputBar.framework; sourceTree = ""; }; - 4C508648221C0BBA0043943C /* AccessoryPosition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessoryPosition.swift; sourceTree = ""; }; 38F8062D2173CD4300CDB9DB /* MockUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUser.swift; sourceTree = ""; }; + 4C508648221C0BBA0043943C /* AccessoryPosition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessoryPosition.swift; sourceTree = ""; }; 5073C1142175BE750040EAD5 /* AudioItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioItem.swift; sourceTree = ""; }; 5073C1182175BE950040EAD5 /* AudioMessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioMessageCell.swift; sourceTree = ""; }; 5073C11C2175BEC60040EAD5 /* AudioMessageSizeCalculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioMessageSizeCalculator.swift; sourceTree = ""; }; 5073C1222175C1980040EAD5 /* sound1.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = sound1.m4a; sourceTree = ""; }; + 50FF34542237FE4C0004DCD7 /* ContactItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactItem.swift; sourceTree = ""; }; + 50FF34562237FE6A0004DCD7 /* UIImage+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = ""; }; + 50FF34582237FE840004DCD7 /* ContactMessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactMessageCell.swift; sourceTree = ""; }; + 50FF345A2237FE9C0004DCD7 /* ContactMessageSizeCalculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactMessageSizeCalculator.swift; sourceTree = ""; }; 88916B221CF0DF2F00469F91 /* MessageKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MessageKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 88916B2C1CF0DF2F00469F91 /* MessageKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MessageKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 8962AC741F87AB230030B058 /* MessageKitDateFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageKitDateFormatterTests.swift; sourceTree = ""; }; @@ -238,6 +245,7 @@ B7A03F381F866946006AEF79 /* LocationMessageCell.swift */, B7A03F391F866946006AEF79 /* MediaMessageCell.swift */, 5073C1182175BE950040EAD5 /* AudioMessageCell.swift */, + 50FF34582237FE840004DCD7 /* ContactMessageCell.swift */, B7A03F7A1F866B85006AEF79 /* MessageCollectionViewCell.swift */, 1F6C040B206A2891007BDE44 /* MessageContentCell.swift */, B7A03F361F866946006AEF79 /* TextMessageCell.swift */, @@ -380,6 +388,7 @@ B09643981F295D43004D0129 /* Extensions */ = { isa = PBXGroup; children = ( + 50FF34562237FE6A0004DCD7 /* UIImage+Extension.swift */, 0EE91E651FDEC887005420A2 /* CGRect+Extensions.swift */, B7A03F671F8669EB006AEF79 /* Bundle+Extensions.swift */, B7A03F681F8669EB006AEF79 /* NSAttributedString+Extensions.swift */, @@ -428,6 +437,7 @@ B096439B1F295D82004D0129 /* Protocols */ = { isa = PBXGroup; children = ( + 50FF34542237FE4C0004DCD7 /* ContactItem.swift */, B7A03F521F8669C9006AEF79 /* MessageCellDelegate.swift */, B7A03F551F8669C9006AEF79 /* MessageLabelDelegate.swift */, B7A03F571F8669CA006AEF79 /* MessagesDataSource.swift */, @@ -455,6 +465,7 @@ B096439D1F295DC3004D0129 /* Layout */ = { isa = PBXGroup; children = ( + 50FF345A2237FE9C0004DCD7 /* ContactMessageSizeCalculator.swift */, B7A03F161F86682C006AEF79 /* MessagesCollectionViewFlowLayout.swift */, B7A03F171F86682C006AEF79 /* MessagesCollectionViewLayoutAttributes.swift */, 1FE7839D20662835007FA024 /* MessageSizeCalculator.swift */, @@ -622,6 +633,7 @@ B7A03F6C1F8669EB006AEF79 /* UIView+Extensions.swift in Sources */, 383B9EB121728BAD008AB91A /* SenderType.swift in Sources */, B7A03F3A1F866946006AEF79 /* TextMessageCell.swift in Sources */, + 50FF34552237FE4C0004DCD7 /* ContactItem.swift in Sources */, B7A03F191F86682C006AEF79 /* MessagesCollectionViewLayoutAttributes.swift in Sources */, 38A2230F221FB8A300D14DAF /* MessageInputBar.swift in Sources */, B7A03F461F86694F006AEF79 /* AvatarView.swift in Sources */, @@ -636,10 +648,12 @@ B7A03F261F866895006AEF79 /* MessageKitDateFormatter.swift in Sources */, B7A03F6D1F8669EB006AEF79 /* Bundle+Extensions.swift in Sources */, 5073C1192175BE960040EAD5 /* AudioMessageCell.swift in Sources */, + 50FF34592237FE850004DCD7 /* ContactMessageCell.swift in Sources */, 1FD589602064E08A004B5081 /* MediaItem.swift in Sources */, B7A03F611F8669CA006AEF79 /* MessagesDataSource.swift in Sources */, B7A03F6E1F8669EB006AEF79 /* NSAttributedString+Extensions.swift in Sources */, B7A03F471F86694F006AEF79 /* MessageLabel.swift in Sources */, + 50FF345B2237FE9C0004DCD7 /* ContactMessageSizeCalculator.swift in Sources */, B7A03F5F1F8669CA006AEF79 /* MessageLabelDelegate.swift in Sources */, B7A03F4C1F86694F006AEF79 /* PlayButtonView.swift in Sources */, 1FD5896420660C1C004B5081 /* LocationItem.swift in Sources */, @@ -660,6 +674,7 @@ 1F6C040C206A2891007BDE44 /* MessageContentCell.swift in Sources */, B7A03F2C1F866895006AEF79 /* DetectorType.swift in Sources */, B7A03F271F866895006AEF79 /* Avatar.swift in Sources */, + 50FF34572237FE6A0004DCD7 /* UIImage+Extension.swift in Sources */, 1F82D1431FB1B75B00B81A88 /* AvatarPosition.swift in Sources */, 1FF377AC20087DA2004FD648 /* MessagesViewController+Keyboard.swift in Sources */, ); From 9fa69e7e596937ba6c34883f71f7a6cc59094576 Mon Sep 17 00:00:00 2001 From: Iosif Moldovan Date: Sat, 30 Mar 2019 11:12:02 +0200 Subject: [PATCH 3/5] Apply code review --- Example/Podfile.lock | 4 +- Sources/Extensions/UIImage+Extension.swift | 4 +- .../Layout/ContactMessageSizeCalculator.swift | 2 +- Sources/Protocols/ContactItem.swift | 33 +++++-------- Sources/Views/Cells/ContactMessageCell.swift | 46 ++++++++----------- 5 files changed, 35 insertions(+), 54 deletions(-) diff --git a/Example/Podfile.lock b/Example/Podfile.lock index ee6d8c08..040868c8 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - InputBarAccessoryView (4.2.1) + - InputBarAccessoryView (4.2.2) - MessageKit (3.0.0-beta): - InputBarAccessoryView @@ -15,7 +15,7 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - InputBarAccessoryView: 37175becdced159cd9ea335f47ae001b1e112909 + InputBarAccessoryView: 2b937602598e2fab3149f37f51dd7ad795653812 MessageKit: 30a1e0b112c44361904016e7add631a78c5128e0 PODFILE CHECKSUM: cecdb7bc8129cf99f66de9f68eea3256fec30c3d diff --git a/Sources/Extensions/UIImage+Extension.swift b/Sources/Extensions/UIImage+Extension.swift index a56fa9d0..157fd0f4 100644 --- a/Sources/Extensions/UIImage+Extension.swift +++ b/Sources/Extensions/UIImage+Extension.swift @@ -22,7 +22,7 @@ SOFTWARE. */ -public enum ImageTye: String { +public enum ImageType: String { case play case pause case disclouser @@ -33,7 +33,7 @@ import UIKit /// This extension provide a way to access image resources with in framework public extension UIImage { - public class func messageKitImageWith(type: ImageTye) -> 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 ?? "") diff --git a/Sources/Layout/ContactMessageSizeCalculator.swift b/Sources/Layout/ContactMessageSizeCalculator.swift index 14d36fa4..f1705411 100644 --- a/Sources/Layout/ContactMessageSizeCalculator.swift +++ b/Sources/Layout/ContactMessageSizeCalculator.swift @@ -50,7 +50,7 @@ open class ContactMessageSizeCalculator: MessageSizeCalculator { switch message.kind { case .contact(let item): - attributedText = NSAttributedString(string: item.getName(), attributes: [.font: contactLabelFont]) + attributedText = NSAttributedString(string: item.displayText, attributes: [.font: contactLabelFont]) default: fatalError("messageContainerSize received unhandled MessageDataType: \(message.kind)") } diff --git a/Sources/Protocols/ContactItem.swift b/Sources/Protocols/ContactItem.swift index 59c36942..e2ae12b8 100644 --- a/Sources/Protocols/ContactItem.swift +++ b/Sources/Protocols/ContactItem.swift @@ -38,47 +38,36 @@ public protocol ContactItem { /// contact emails var emails: [String] { get } - +} + +/// Default Implementation to get contact name and initials +public extension ContactItem { + /// 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 { + public var displayText: 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 + if name.isEmpty { // if name is still 0 show first phone number name = phoneNumbers.first ?? "" } - if name.count == 0 { // if name is still 0 show first email + if name.isEmpty { // if name is still 0 show first email name = emails.first ?? "" } return name } - func getInitials() -> String { - var initials = getFirstLeterFrom(firstName) + getFirstLeterFrom(lastName) + /// Return name initials from given contact. If first and last name is not set it returns # + public var initials: String { + var initials = String(firstName?.prefix(1) ?? "") + String(lastName?.prefix(1) ?? "") 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/ContactMessageCell.swift b/Sources/Views/Cells/ContactMessageCell.swift index 01e5594d..9e3d7934 100644 --- a/Sources/Views/Cells/ContactMessageCell.swift +++ b/Sources/Views/Cells/ContactMessageCell.swift @@ -26,64 +26,56 @@ import UIKit open class ContactMessageCell: MessageContentCell { - public enum ConstrainsID: String { + public enum ConstraintsID: String { case initialsContainerLeftConstraint case disclouserRigtConstraint } /// The view container that holds contact initials - open lazy var initialsContainerView: UIView = { + public 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 = { + 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 - open lazy var nameLabel: UILabel = { + public lazy var nameLabel: UILabel = { let nameLabel = UILabel(frame: CGRect.zero) nameLabel.numberOfLines = 0 - return nameLabel }() /// The disclouser image view - open lazy var disclouserImageView: UIImageView = { + 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) - if let attributes = layoutAttributes as? MessagesCollectionViewLayoutAttributes { - nameLabel.font = attributes.messageLabelFont + 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(disclouserImageView) + messageContainerView.addSubview(disclosureImageView) initialsContainerView.addSubview(initialsLabel) - setupConstraints() } @@ -97,17 +89,17 @@ open class ContactMessageCell: MessageContentCell { 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 + initialsConstraints.first?.identifier = ConstraintsID.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, + disclosureImageView.constraint(equalTo: CGSize(width: 20, height: 20)) + let disclosureConstraints = disclosureImageView.addConstraints(right: messageContainerView.rightAnchor, centerY: messageContainerView.centerYAnchor, rightConstant: -10) - disclouserConstraints.first?.identifier = ConstrainsID.disclouserRigtConstraint.rawValue + disclosureConstraints.first?.identifier = ConstraintsID.disclouserRigtConstraint.rawValue nameLabel.addConstraints(messageContainerView.topAnchor, left: initialsContainerView.rightAnchor, bottom: messageContainerView.bottomAnchor, - right: disclouserImageView.leftAnchor, + right: disclosureImageView.leftAnchor, topConstant: 0, leftConstant: 10, bottomConstant: 0, @@ -119,17 +111,17 @@ open class ContactMessageCell: MessageContentCell { 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() + nameLabel.text = contactItem.displayText + 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 == ConstrainsID.initialsContainerLeftConstraint.rawValue + return constraint.identifier == ConstraintsID.initialsContainerLeftConstraint.rawValue }.first let disclouserRightConstraint = messageContainerView.constraints.filter { (constraint) -> Bool in - return constraint.identifier == ConstrainsID.disclouserRigtConstraint.rawValue + return constraint.identifier == ConstraintsID.disclouserRigtConstraint.rawValue }.first if dataSource.isFromCurrentSender(message: message) { // outgoing message initialsContainerLeftConstraint?.constant = 5 @@ -144,7 +136,7 @@ open class ContactMessageCell: MessageContentCell { } let textColor = displayDelegate.textColor(for: message, at: indexPath, in: messagesCollectionView) nameLabel.textColor = textColor - disclouserImageView.tintColor = textColor + disclosureImageView.tintColor = textColor } } From a0c7878e1f7670cea91c63491396dab619314531 Mon Sep 17 00:00:00 2001 From: Iosif Moldovan Date: Sat, 30 Mar 2019 11:28:49 +0200 Subject: [PATCH 4/5] update change log --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c8b54e9..a637d4de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ The changelog for `MessageKit`. Also see the [releases](https://github.com/Messa ### Changed +- **Breaking Change** Add support for share contact. [#1013](https://github.com/MessageKit/MessageKit/pull/1013) by [@moldovaniosif](https://github.com/moldovaniosif) + - **Breaking Change** Deprecated the Sender struct in favor of the `SenderType` protocol. [#909](https://github.com/MessageKit/MessageKit/pull/909) by [@nathantannar4](https://github.com/nathantannar4) - **Breaking Change** Add support for audio messages [#892](https://github.com/MessageKit/MessageKit/pull/892) by [@moldovaniosif](https://github.com/moldovaniosif). Added new protocols `AudioControllerDelegate`, `AudioItem` a new cell `AudioMessageCell` and a new controller `BasicAudioController`. From cdedd2bf8caf8541f1de22f7cb8ba0f9598061db Mon Sep 17 00:00:00 2001 From: Iosif Moldovan Date: Thu, 4 Apr 2019 15:09:59 +0300 Subject: [PATCH 5/5] apply code review --- .../Sources/Data Generation/SampleData.swift | 12 +++--- Example/Sources/Models/MockMessage.swift | 10 ++--- .../Layout/ContactMessageSizeCalculator.swift | 2 +- Sources/Protocols/ContactItem.swift | 40 ++----------------- Sources/Views/Cells/AudioMessageCell.swift | 6 +-- Sources/Views/Cells/ContactMessageCell.swift | 2 +- 6 files changed, 20 insertions(+), 52 deletions(-) diff --git a/Example/Sources/Data Generation/SampleData.swift b/Example/Sources/Data Generation/SampleData.swift index 3bab53ba..d537beae 100644 --- a/Example/Sources/Data Generation/SampleData.swift +++ b/Example/Sources/Data Generation/SampleData.swift @@ -54,12 +54,12 @@ final internal class SampleData { 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"]) + MockContactItem(name: "System", initials: "S"), + MockContactItem(name: "Nathan Tannar", initials: "NT", emails: ["test@test.com"]), + MockContactItem(name: "Steven Deutsch", initials: "SD", phoneNumbers: ["+1-202-555-0114", "+1-202-555-0145"]), + MockContactItem(name: "Wu Zhong", initials: "WZ", phoneNumbers: ["202-555-0158"]), + MockContactItem(name:"+40 123 123", initials: "#", phoneNumbers: ["+40 123 123"]), + MockContactItem(name:"test@test.com", initials: "#", emails: ["test@test.com"]) ] var currentSender: MockUser { diff --git a/Example/Sources/Models/MockMessage.swift b/Example/Sources/Models/MockMessage.swift index b188d20a..54e758c6 100644 --- a/Example/Sources/Models/MockMessage.swift +++ b/Example/Sources/Models/MockMessage.swift @@ -72,14 +72,14 @@ private struct MockAudiotem: AudioItem { struct MockContactItem: ContactItem { - var firstName: String? - var lastName: String? + var displayName: String + var initials: String var phoneNumbers: [String] var emails: [String] - init(firstName: String? = nil, lastName: String? = nil, phoneNumbers: [String] = [], emails: [String] = []) { - self.firstName = firstName - self.lastName = lastName + init(name: String, initials: String, phoneNumbers: [String] = [], emails: [String] = []) { + self.displayName = name + self.initials = initials self.phoneNumbers = phoneNumbers self.emails = emails } diff --git a/Sources/Layout/ContactMessageSizeCalculator.swift b/Sources/Layout/ContactMessageSizeCalculator.swift index f1705411..e45b61ee 100644 --- a/Sources/Layout/ContactMessageSizeCalculator.swift +++ b/Sources/Layout/ContactMessageSizeCalculator.swift @@ -50,7 +50,7 @@ open class ContactMessageSizeCalculator: MessageSizeCalculator { switch message.kind { case .contact(let item): - attributedText = NSAttributedString(string: item.displayText, attributes: [.font: contactLabelFont]) + attributedText = NSAttributedString(string: item.displayName, attributes: [.font: contactLabelFont]) default: fatalError("messageContainerSize received unhandled MessageDataType: \(message.kind)") } diff --git a/Sources/Protocols/ContactItem.swift b/Sources/Protocols/ContactItem.swift index e2ae12b8..f43e842d 100644 --- a/Sources/Protocols/ContactItem.swift +++ b/Sources/Protocols/ContactItem.swift @@ -27,11 +27,11 @@ import Foundation /// A protocol used to represent the data for a contact message. public protocol ContactItem { - /// contact first name - var firstName: String? { get } + /// contact displayed name + var displayName: String { get } - /// contact last name - var lastName: String? { get } + /// initials from contact first and last name + var initials: String { get } /// contact phone numbers var phoneNumbers: [String] { get } @@ -39,35 +39,3 @@ public protocol ContactItem { /// contact emails var emails: [String] { get } } - -/// Default Implementation to get contact name and initials -public extension ContactItem { - - /// 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 - public var displayText: String { - var name = firstName ?? "" - if let lastName = lastName, lastName.count > 0 { - name += (name.count > 0) ? " \(lastName)" : lastName - } - if name.isEmpty { // if name is still 0 show first phone number - name = phoneNumbers.first ?? "" - } - if name.isEmpty { // if name is still 0 show first email - name = emails.first ?? "" - } - return name - } - - /// Return name initials from given contact. If first and last name is not set it returns # - public var initials: String { - var initials = String(firstName?.prefix(1) ?? "") + String(lastName?.prefix(1) ?? "") - if initials.count == 0 { - initials = "#" - } - return initials - } -} diff --git a/Sources/Views/Cells/AudioMessageCell.swift b/Sources/Views/Cells/AudioMessageCell.swift index b672ca88..a9251ce5 100644 --- a/Sources/Views/Cells/AudioMessageCell.swift +++ b/Sources/Views/Cells/AudioMessageCell.swift @@ -29,7 +29,7 @@ import AVFoundation open class AudioMessageCell: MessageContentCell { /// The play button view to display on audio messages. - open lazy var playButton: UIButton = { + public lazy var playButton: UIButton = { let playButton = UIButton(type: .custom) let playImage = UIImage.messageKitImageWith(type: .play) let pauseImage = UIImage.messageKitImageWith(type: .pause) @@ -39,7 +39,7 @@ open class AudioMessageCell: MessageContentCell { }() /// The time duration lable to display on audio messages. - open lazy var durationLabel: UILabel = { + public lazy var durationLabel: UILabel = { let durationLabel = UILabel(frame: CGRect.zero) durationLabel.textAlignment = .right durationLabel.font = UIFont.systemFont(ofSize: 14) @@ -47,7 +47,7 @@ open class AudioMessageCell: MessageContentCell { return durationLabel }() - open lazy var progressView: UIProgressView = { + public lazy var progressView: UIProgressView = { let progressView = UIProgressView(progressViewStyle: .default) progressView.progress = 0.0 return progressView diff --git a/Sources/Views/Cells/ContactMessageCell.swift b/Sources/Views/Cells/ContactMessageCell.swift index 9e3d7934..d7f50087 100644 --- a/Sources/Views/Cells/ContactMessageCell.swift +++ b/Sources/Views/Cells/ContactMessageCell.swift @@ -111,7 +111,7 @@ open class ContactMessageCell: MessageContentCell { 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.displayText + nameLabel.text = contactItem.displayName initialsLabel.text = contactItem.initials // setup constraints guard let dataSource = messagesCollectionView.messagesDataSource else {