diff --git a/Assets/MessageKitAssets.bundle/Images/disclouser.png b/Assets/MessageKitAssets.bundle/Images/disclouser.png new file mode 100644 index 00000000..1c5a8c9e Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/disclouser.png differ diff --git a/Assets/MessageKitAssets.bundle/Images/disclouser@2x.png b/Assets/MessageKitAssets.bundle/Images/disclouser@2x.png new file mode 100644 index 00000000..8ef8385c Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/disclouser@2x.png differ diff --git a/Assets/MessageKitAssets.bundle/Images/disclouser@3x.png b/Assets/MessageKitAssets.bundle/Images/disclouser@3x.png new file mode 100644 index 00000000..cc09ac13 Binary files /dev/null and b/Assets/MessageKitAssets.bundle/Images/disclouser@3x.png differ diff --git a/CHANGELOG.md b/CHANGELOG.md index ab2ece43..7aba7f2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ The changelog for `MessageKit`. Also see the [releases](https://github.com/Messa - **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** 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. Added new protocols `AudioControllerDelegate`, `AudioItem` a new cell `AudioMessageCell` and a new controller `BasicAudioController`. [#892](https://github.com/MessageKit/MessageKit/pull/892) by [@moldovaniosif](https://github.com/moldovaniosif). @@ -24,6 +26,8 @@ The changelog for `MessageKit`. Also see the [releases](https://github.com/Messa ### Added +- **Breaking Change** Add support for share contact. [#1013](https://github.com/MessageKit/MessageKit/pull/1013) by [@moldovaniosif](https://github.com/moldovaniosif) + - Added typing indicator support, `func setTypingIndicatorViewHidden(_ isHidden: Bool, animated: Bool, whilePerforming updates: (() -> Void)? = nil, completion: ((Bool) -> Void)? = nil)`. Return a custom typing view by conforming to `MessagesDisplayDelegate` or use the [default appearance](https://github.com/nathantannar4/TypingIndicator). Customize the size with `MessagesLayoutDelegate` . [#989](https://github.com/MessageKit/MessageKit/pull/911) by [@nathantannar4](https://github.com/nathantannar4) diff --git a/Example/ChatExample.xcodeproj/project.pbxproj b/Example/ChatExample.xcodeproj/project.pbxproj index 7cc41a7f..7be14b8c 100644 --- a/Example/ChatExample.xcodeproj/project.pbxproj +++ b/Example/ChatExample.xcodeproj/project.pbxproj @@ -477,7 +477,7 @@ files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ChatExample/Pods-ChatExample-frameworks.sh", + "${SRCROOT}/Pods/Target Support Files/Pods-ChatExample/Pods-ChatExample-frameworks.sh", "${BUILT_PRODUCTS_DIR}/InputBarAccessoryView/InputBarAccessoryView.framework", "${BUILT_PRODUCTS_DIR}/MessageKit/MessageKit.framework", ); @@ -488,7 +488,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ChatExample/Pods-ChatExample-frameworks.sh\"\n"; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ChatExample/Pods-ChatExample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 52AB594E03799821A66AFF18 /* [CP] Check Pods Manifest.lock */ = { diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 0510f35a..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,9 +15,9 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - InputBarAccessoryView: 37175becdced159cd9ea335f47ae001b1e112909 - MessageKit: 4226995f0811d3a51b8e80b56d2e95c97fa879e8 + InputBarAccessoryView: 2b937602598e2fab3149f37f51dd7ad795653812 + MessageKit: 30a1e0b112c44361904016e7add631a78c5128e0 PODFILE CHECKSUM: cecdb7bc8129cf99f66de9f68eea3256fec30c3d -COCOAPODS: 1.6.1 +COCOAPODS: 1.5.3 diff --git a/Example/Sources/Data Generation/SampleData.swift b/Example/Sources/Data Generation/SampleData.swift index 5c782883..69ee97d4 100644 --- a/Example/Sources/Data Generation/SampleData.swift +++ b/Example/Sources/Data Generation/SampleData.swift @@ -43,6 +43,7 @@ final internal class SampleData { case Url case Phone case Custom + case ShareContact } let system = MockUser(senderId: "000000", displayName: "System") @@ -51,6 +52,15 @@ final internal class SampleData { let wu = MockUser(senderId: "000003", displayName: "Wu Zhong") lazy var senders = [nathan, steven, wu] + + lazy var contactsToShare = [ + 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 { return nathan @@ -142,8 +152,8 @@ final internal class SampleData { return messageTypes.random()! } + // swiftlint:disable cyclomatic_complexity func randomMessage(allowedSenders: [MockUser]) -> MockMessage { - let randomNumberSender = Int(arc4random_uniform(UInt32(allowedSenders.count))) let uniqueID = NSUUID().uuidString @@ -182,8 +192,12 @@ final internal class SampleData { return MockMessage(text: "123-456-7890", user: user, messageId: uniqueID, date: date) case .Custom: return MockMessage(custom: "Someone left the conversation", user: system, messageId: uniqueID, date: date) + case .ShareContact: + let randomContact = Int(arc4random_uniform(UInt32(contactsToShare.count))) + return MockMessage(contact: contactsToShare[randomContact], user: user, messageId: uniqueID, date: date) } } + // swiftlint:enable cyclomatic_complexity func getMessages(count: Int, completion: ([MockMessage]) -> Void) { var messages: [MockMessage] = [] diff --git a/Example/Sources/Models/MockMessage.swift b/Example/Sources/Models/MockMessage.swift index 5706c2d6..ef6d0f84 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 displayName: String + var initials: String + var phoneNumbers: [String] + var emails: [String] + + init(name: String, initials: String, phoneNumbers: [String] = [], emails: [String] = []) { + self.displayName = name + self.initials = initials + self.phoneNumbers = phoneNumbers + self.emails = emails + } + +} + internal struct MockMessage: MessageType { var messageId: String @@ -124,4 +140,7 @@ internal struct MockMessage: MessageType { self.init(kind: .audio(audioItem), user: user, messageId: messageId, date: date) } + init(contact: MockContactItem, user: MockUser, messageId: String, date: Date) { + self.init(kind: .contact(contact), user: user, messageId: messageId, date: date) + } } diff --git a/Example/Sources/View Controllers/SettingsViewController.swift b/Example/Sources/View Controllers/SettingsViewController.swift index aa8291ea..92235e52 100644 --- a/Example/Sources/View Controllers/SettingsViewController.swift +++ b/Example/Sources/View Controllers/SettingsViewController.swift @@ -33,7 +33,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 @@ -137,6 +137,7 @@ final internal class SettingsViewController: UITableViewController { let cell = cells[sender.tag] UserDefaults.standard.set(sender.isOn, forKey: cell) + UserDefaults.standard.synchronize() } } diff --git a/MessageKit.xcodeproj/project.pbxproj b/MessageKit.xcodeproj/project.pbxproj index 8c005e87..57ad9a52 100644 --- a/MessageKit.xcodeproj/project.pbxproj +++ b/MessageKit.xcodeproj/project.pbxproj @@ -50,6 +50,10 @@ 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 */; }; @@ -161,6 +165,10 @@ 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 = ""; }; @@ -250,6 +258,7 @@ B7A03F381F866946006AEF79 /* LocationMessageCell.swift */, B7A03F391F866946006AEF79 /* MediaMessageCell.swift */, 5073C1182175BE950040EAD5 /* AudioMessageCell.swift */, + 50FF34582237FE840004DCD7 /* ContactMessageCell.swift */, B7A03F7A1F866B85006AEF79 /* MessageCollectionViewCell.swift */, 1F6C040B206A2891007BDE44 /* MessageContentCell.swift */, B7A03F361F866946006AEF79 /* TextMessageCell.swift */, @@ -393,6 +402,7 @@ B09643981F295D43004D0129 /* Extensions */ = { isa = PBXGroup; children = ( + 50FF34562237FE6A0004DCD7 /* UIImage+Extension.swift */, 0EE91E651FDEC887005420A2 /* CGRect+Extensions.swift */, B7A03F671F8669EB006AEF79 /* Bundle+Extensions.swift */, B7A03F681F8669EB006AEF79 /* NSAttributedString+Extensions.swift */, @@ -444,6 +454,7 @@ B096439B1F295D82004D0129 /* Protocols */ = { isa = PBXGroup; children = ( + 50FF34542237FE4C0004DCD7 /* ContactItem.swift */, B7A03F521F8669C9006AEF79 /* MessageCellDelegate.swift */, B7A03F551F8669C9006AEF79 /* MessageLabelDelegate.swift */, B7A03F571F8669CA006AEF79 /* MessagesDataSource.swift */, @@ -471,6 +482,7 @@ B096439D1F295DC3004D0129 /* Layout */ = { isa = PBXGroup; children = ( + 50FF345A2237FE9C0004DCD7 /* ContactMessageSizeCalculator.swift */, B7A03F161F86682C006AEF79 /* MessagesCollectionViewFlowLayout.swift */, B7A03F171F86682C006AEF79 /* MessagesCollectionViewLayoutAttributes.swift */, 1FE7839D20662835007FA024 /* MessageSizeCalculator.swift */, @@ -641,6 +653,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 */, @@ -656,10 +669,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 */, @@ -682,6 +697,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 */, ); diff --git a/Sources/Controllers/MessagesViewController.swift b/Sources/Controllers/MessagesViewController.swift index f9336f19..ed4f9cd8 100644 --- a/Sources/Controllers/MessagesViewController.swift +++ b/Sources/Controllers/MessagesViewController.swift @@ -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) } diff --git a/Sources/Extensions/UIImage+Extension.swift b/Sources/Extensions/UIImage+Extension.swift new file mode 100644 index 00000000..157fd0f4 --- /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 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 + } + +} diff --git a/Sources/Layout/ContactMessageSizeCalculator.swift b/Sources/Layout/ContactMessageSizeCalculator.swift new file mode 100644 index 00000000..e45b61ee --- /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.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 + } + +} diff --git a/Sources/Layout/MessagesCollectionViewFlowLayout.swift b/Sources/Layout/MessagesCollectionViewFlowLayout.swift index 7d8c2d2b..b5e76738 100644 --- a/Sources/Layout/MessagesCollectionViewFlowLayout.swift +++ b/Sources/Layout/MessagesCollectionViewFlowLayout.swift @@ -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 ] } diff --git a/Sources/Models/MessageKind.swift b/Sources/Models/MessageKind.swift index 78a971e6..8a8eea48 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..f43e842d --- /dev/null +++ b/Sources/Protocols/ContactItem.swift @@ -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 } +} diff --git a/Sources/Views/Cells/AudioMessageCell.swift b/Sources/Views/Cells/AudioMessageCell.swift index 295aad3c..a9251ce5 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 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) diff --git a/Sources/Views/Cells/ContactMessageCell.swift b/Sources/Views/Cells/ContactMessageCell.swift new file mode 100644 index 00000000..d7f50087 --- /dev/null +++ b/Sources/Views/Cells/ContactMessageCell.swift @@ -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 + } + +} diff --git a/Sources/Views/MessagesCollectionView.swift b/Sources/Views/MessagesCollectionView.swift index 07dbdcac..22e75474 100644 --- a/Sources/Views/MessagesCollectionView.swift +++ b/Sources/Views/MessagesCollectionView.swift @@ -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)