Last active
January 26, 2018 16:15
-
-
Save dodikk/c0f0bb7c00380e9fe064e226f3909505 to your computer and use it in GitHub Desktop.
Bubble code for NMessenger ticket #115
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import AsyncDisplayKit | |
import NMessenger | |
import VHChatLogic | |
class ChatRoomView: NMessengerViewController, ViewType | |
{ | |
public var controller: ChatRoomController? | |
private let vhBubbleConfig = VHBubbleConfiguration() | |
override func viewDidLoad() | |
{ | |
super.viewDidLoad() | |
precondition(nil != self.controller) | |
self.controller?.delegate = self | |
super.sharedBubbleConfiguration = self.vhBubbleConfig | |
super.messagePadding = UIEdgeInsets(top: 30, left: 0, bottom: 30, right: 0) | |
self.messengerView.doesBatchFetch = true | |
self.controller?.getInitialHistoryAsync() | |
} | |
// MARK: - NMessengerViewController | |
// | |
// | |
override func sendText(_ text: String, isIncomingMessage:Bool) -> GeneralMessengerCell | |
{ | |
let shouldSendToServer = !isIncomingMessage | |
if (shouldSendToServer) | |
{ | |
self.controller?.sendMessageAsync(text) | |
} | |
let result = self.buildNodeForTextMessage(text, isIncomingMessage: isIncomingMessage) | |
self.addMessageToMessenger(result) | |
return result | |
} | |
fileprivate func buildNodeForTextMessage(_ text: String, isIncomingMessage:Bool) -> GeneralMessengerCell | |
{ | |
// construct a node : "text bubble + avatar" | |
// | |
let contentNode = TextContentNode(textMessageString: text, | |
currentViewController: self, | |
bubbleConfiguration: self.vhBubbleConfig) | |
contentNode.incomingTextColor = UIColor.black | |
contentNode.outgoingTextColor = UIColor.black | |
// TODO: configure fonts | |
let mockTimestampStr = "4:07 pm \n\n" | |
let timestampStr = mockTimestampStr | |
let result = MessageNode(content: contentNode) | |
result.isIncomingMessage = isIncomingMessage | |
let avatar = ASImageNode() | |
avatar.image = self.myAvatarMock() | |
// https://github.com/eBay/NMessenger/issues/35 | |
// | |
avatar.preferredFrameSize = CGSize(width: 35, height: 35) | |
result.avatarNode = avatar | |
// timestamp customization goes last | |
// due to side effects from setters `result.isIncomingMessage =` | |
if let messagePart = contentNode.textMessageString | |
{ | |
let timestampColor = UIColor.lightGray | |
//UIColor.lightGray | |
let timestampAttributes = | |
[ | |
NSStrokeColorAttributeName: timestampColor, | |
NSForegroundColorAttributeName: timestampColor | |
] | |
let timestampAddendum = NSAttributedString(string: timestampStr, | |
attributes: timestampAttributes) | |
let timestampAndMessage = NSMutableAttributedString(attributedString: timestampAddendum) | |
timestampAndMessage.append(messagePart) | |
contentNode.textMessageString = timestampAndMessage | |
} | |
// let timestamp = ASTextNode() | |
// timestamp.attributedText = NSAttributedString(string: "4:07 pm") | |
// | |
// result.headerNode = timestamp | |
// http://asyncdisplaykit.org/docs/automatic-layout-examples-2.html | |
// let isDebugging = false | |
// if (isDebugging) | |
// { | |
// result.backgroundColor = UIColor.orange | |
// result.cornerRadius = 0.1 | |
// | |
// timestamp.backgroundColor = UIColor.cyan | |
// avatar.backgroundColor = UIColor.green | |
// } | |
return result | |
} | |
// MARK: - NMessengerDelegate | |
func batchFetchContent() | |
{ | |
print("batchFetchContent") | |
// let numberOfMessagesToPreload = 20 | |
// let messageCount = 10 | |
// let upperLimit = messageCount + numberOfMessagesToPreload | |
// | |
// let range = Range(uncheckedBounds: (lower: messageCount, upper: upperLimit)) | |
// self.controller?.preloadMessagesFor(range: range) | |
} | |
// MARK: - ViewType | |
// | |
// not in extension due to some compiler errors | |
// | |
public func update(state: MessageListState, oldState: MessageListState?) | |
{ | |
// TODO: implement me | |
} | |
private func myAvatarMock() -> UIImage | |
{ | |
// let result = UIImage(named: "test-avatar-sender") | |
let result = UIImage(named: "Carrie") | |
return result! | |
} | |
private func otherUserAvatarMock() -> UIImage | |
{ | |
// let result = UIImage(named: "test-avatar-receiver") | |
let result = UIImage(named: "Jessie") | |
return result! | |
} | |
} | |
extension ChatRoomView : ChatRoomDelegate | |
{ | |
public func chatDidReceiveInitialHistory(_ messageList: ChatMessageList) | |
{ | |
self.messengerView.clearALLMessages() | |
messageList.forEach | |
{ | |
let messageCell = self.buildNodeForTextMessage($0.text, isIncomingMessage: $0.isIncoming) | |
self.addMessageToMessenger(messageCell) | |
} | |
} | |
public func chatDidFailInitialHistory(_ error: Error) | |
{ | |
// TODO: show alert | |
} | |
func chatDidSendMessage(_ message: ChatMessage) | |
{ | |
// _ = self.sendText(message.text, isIncomingMessage: false) | |
// IDLE | |
} | |
func chatDidFailToSendMessage(_ message: ChatMessage, withError: Error) | |
{ | |
// TODO: remove message from chat view | |
// | |
} | |
func chatDidReceiveMessages(_ messageList: ChatMessageList) | |
{ | |
messageList.forEach | |
{ | |
let messageNode = self.buildNodeForTextMessage($0.text, isIncomingMessage: true) | |
self.addMessageToMessenger(messageNode) | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// VHBubble.swift | |
// | |
// Created by Alexander Dodatko on 4/5/17. | |
// | |
import Foundation | |
// | |
// Copyright (c) 2016 eBay Software Foundation | |
// | |
// 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 | |
import UIKit | |
import NMessenger | |
/** | |
VHBubble has four rounded corners and a triangle pointing to the avatar. | |
*/ | |
open class VHBubble: Bubble { | |
//MARK: Public Variables | |
/** Radius of the corners for the bubble. When this is set, you will need to call setNeedsLayout on your message for changes to take effect if the bubble has already been drawn*/ | |
open var radius : CGFloat = 16 | |
/** Should be less or equal to the *radius* property. When this is set, you will need to call setNeedsLayout on your message for changes to take effect if the bubble has already been drawn*/ | |
open var borderWidth : CGFloat = 4 //TODO: | |
/** The color of the border around the bubble. When this is set, you will need to call setNeedsLayout on your message for changes to take effect if the bubble has already been drawn*/ | |
public var triangleHeight: CGFloat = 10 | |
open var bubbleBorderColor : UIColor = UIColor(red: 0xFD / 255.0, | |
green: 0xD6 / 255.0, | |
blue: 0xCC / 255.0, | |
alpha: 1) | |
/** Path used to cutout the bubble*/ | |
open fileprivate(set) var path: CGMutablePath = CGMutablePath() | |
// MARK: Initialisers | |
/** | |
Initialiser class. | |
*/ | |
public override init() { | |
super.init() | |
self.bubbleColor = UIColor.white | |
} | |
// MARK: Class methods | |
/** | |
Overriding sizeToBounds from super class | |
-parameter bounds: The bounds of the content | |
*/ | |
open override func sizeToBounds(_ bounds: CGRect) { | |
super.sizeToBounds(bounds) | |
var rect = CGRect.zero | |
var radius2: CGFloat = 0 | |
let radius = self.radius | |
let borderWidth = self.borderWidth | |
let diameter = 2*radius | |
if bounds.width < diameter || bounds.height < diameter | |
{ | |
//if the rect calculation yeilds a negative result | |
let newRadiusW = bounds.width/2 | |
let newRadiusH = bounds.height/2 | |
let newRadius = | |
(newRadiusW>newRadiusH) | |
? newRadiusH | |
: newRadiusW | |
let newDiameter = 2*newRadius | |
rect = CGRect(x: newRadius, | |
y: newRadius, | |
width: bounds.width - newDiameter, | |
height: bounds.height - newDiameter) | |
radius2 = newRadius - (borderWidth / 2) | |
self.buildLegacyPath(rect: rect, radius2: radius2) | |
} | |
else | |
{ | |
rect = CGRect(x: radius, | |
y: radius, | |
width: bounds.width - 2*radius, | |
height: bounds.height - 2*radius) | |
radius2 = radius - (borderWidth / 2) | |
self.buildPathWithTriangle(rect: bounds, radius2: radius2) | |
} | |
} | |
private func buildPathWithTriangle(rect: CGRect, | |
radius2: CGFloat) | |
{ | |
// copy-pasted from SO. Replaced some code to make compiler happy. | |
// http://stackoverflow.com/questions/11196112/speech-bubble-in-ios-sdk-using-objective-c | |
//let LINE_WIDTH: CGFloat = 2 | |
let TRIANGLE_HEIGHT: CGFloat = 16; | |
let radius: CGFloat = radius2 | |
let minx = rect.minX | |
let midx = rect.midX | |
let maxx = rect.maxX | |
let miny = rect.minY | |
let midy = rect.midY | |
let maxy = rect.maxY | |
let TRIANGLE_MID_Y = miny + 2 * TRIANGLE_HEIGHT; | |
// TODO: uncomment if looks bad | |
// | |
// rect.origin.y += LINE_WIDTH; | |
// rect.size.width -= LINE_WIDTH * 2; | |
// rect.size.height -= LINE_WIDTH * 2; | |
let outlinePath = CGMutablePath() | |
// TODO: uncomment if looks bad | |
// | |
// minx += TRIANGLE_HEIGHT; | |
// maxx -= TRIANGLE_HEIGHT; | |
let topMid = CGPoint(x: midx, y: miny) | |
let topLeft = CGPoint(x: minx, y: miny) | |
let midLeft = CGPoint(x: minx, y: midy) | |
let bottomLeft = CGPoint(x: minx, y: maxy) | |
let bottomMid = CGPoint(x: midx, y: maxy) | |
let bottomRight = CGPoint(x: maxx, y: maxy) | |
let midRight = CGPoint(x: maxx, y: midy) | |
let topRight = CGPoint(x: maxx, y: miny) | |
let triangleBottomVertex = | |
CGPoint(x: maxx, | |
y: miny + TRIANGLE_MID_Y + TRIANGLE_HEIGHT) | |
let trianglePeakVertex = | |
CGPoint(x: maxx + TRIANGLE_HEIGHT, | |
y: miny + TRIANGLE_MID_Y) | |
let triangleTopVertex = | |
CGPoint(x: maxx, | |
y: miny + TRIANGLE_MID_Y - TRIANGLE_HEIGHT) | |
// top mid | |
//CGPathMoveToPoint(outlinePath, &noTransform, midx, miny); | |
outlinePath.move(to: topMid) | |
// top mid ==> top left ==> mid left ==> ... | |
//CGPathAddArcToPoint (outlinePath, &noTransform, minx, miny, minx, midy, radius); | |
outlinePath.addArc(tangent1End: topLeft, tangent2End: midLeft, radius: radius) | |
// mid left ==> bottom left ==> bottom mid ==> ... | |
// CGPathAddArcToPoint (outlinePath, nil, minx, maxy, midx, maxy, radius); | |
outlinePath.addArc(tangent1End: bottomLeft, tangent2End: bottomMid, radius: radius) | |
// bottom mid ==> bottom right ==> mid right ==> ... | |
// CGPathAddArcToPoint (outlinePath, nil, maxx, maxy, maxx, midy, radius); | |
outlinePath.addArc(tangent1End: bottomRight, tangent2End: midRight, radius: radius) | |
// draw right triangle | |
// | |
outlinePath.addLine(to: triangleBottomVertex) | |
outlinePath.addLine(to: trianglePeakVertex) | |
outlinePath.addLine(to: triangleTopVertex) | |
// CGPathAddLineToPoint(outlinePath, nil, maxx, miny + TRIANGLE_MID_Y + TRIANGLE_HEIGHT); | |
// CGPathAddLineToPoint(outlinePath, nil, maxx + TRIANGLE_HEIGHT, miny + TRIANGLE_MID_Y); | |
// CGPathAddLineToPoint(outlinePath, nil, maxx, miny + TRIANGLE_MID_Y - TRIANGLE_HEIGHT); | |
// mid right ==> top right ==> tom mid | |
// CGPathAddArcToPoint (outlinePath, nil, maxx, miny, midx, miny, radius); | |
outlinePath.addArc(tangent1End: topRight, tangent2End: topMid, radius: radius) | |
outlinePath.closeSubpath() | |
self.path = outlinePath | |
} | |
private func buildLegacyPath(rect: CGRect, | |
radius2: CGFloat) | |
{ | |
self.path = CGMutablePath() | |
self.path.addArc(center: CGPoint(x: rect.maxX, y: rect.minY), | |
radius: radius2, | |
startAngle: CGFloat(-M_PI_2), | |
endAngle: 0, | |
clockwise: false) | |
self.path.addLine(to: CGPoint(x: rect.maxX + radius2, | |
y: rect.maxY + radius2)) | |
self.path.addArc(center: CGPoint(x: rect.minX, y: rect.maxY), | |
radius: radius2, | |
startAngle: CGFloat(M_PI_2), | |
endAngle: CGFloat(M_PI), | |
clockwise: false) | |
self.path.addArc(center: CGPoint(x: rect.minX, y: rect.minY), | |
radius: radius2, | |
startAngle: CGFloat(M_PI), | |
endAngle: CGFloat(-M_PI_2), | |
clockwise: false) | |
//CGPathAddArc(path, nil, rect.maxX, rect.minY, radius2, CGFloat(-M_PI_2), 0, false) | |
//CGPathAddLineToPoint(path, nil, rect.maxX + radius2, rect.maxY + radius2) | |
//CGPathAddArc(path, nil, rect.minX, rect.maxY, radius2, CGFloat(M_PI_2), CGFloat(M_PI), false) | |
//CGPathAddArc(path, nil, rect.minX, rect.minY, radius2, CGFloat(M_PI), CGFloat(-M_PI_2), false) | |
self.path.closeSubpath() | |
} | |
/** | |
Overriding createLayer from super class | |
*/ | |
open override func createLayer() { | |
super.createLayer() | |
CATransaction.begin() | |
CATransaction.setDisableActions(true) | |
self.layer.path = path | |
self.layer.fillColor = self.bubbleColor.cgColor | |
self.layer.strokeColor = self.bubbleBorderColor.cgColor | |
self.layer.lineWidth = self.borderWidth | |
self.layer.position = CGPoint.zero | |
self.maskLayer.fillColor = self.bubbleBorderColor.cgColor | |
self.maskLayer.path = path | |
self.maskLayer.position = CGPoint.zero | |
CATransaction.commit() | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// VHBubbleConfiguration.swift | |
// | |
// Created by Alexander Dodatko on 4/3/17. | |
// | |
import Foundation | |
import NMessenger | |
public class VHBubbleConfiguration: BubbleConfigurationProtocol | |
{ | |
public var isMasked: Bool = true | |
/** Create and return a UI color representing an incoming message */ | |
public func getIncomingColor() -> UIColor | |
{ | |
return UIColor.white | |
} | |
/** Create and return a UI color representing an outgoing message */ | |
public func getOutgoingColor() -> UIColor | |
{ | |
return UIColor.white | |
} | |
/** Create and return a bubble for the ContentNode */ | |
public func getBubble() -> Bubble | |
{ | |
let result = VHBubble() | |
result.hasLayerMask = isMasked | |
return result | |
} | |
/** Create and return a bubble that is used by the Message group for Message nodes after the first. This is typically used to "stack" messages */ | |
public func getSecondaryBubble() -> Bubble | |
{ | |
let newBubble = StackedBubble() | |
newBubble.hasLayerMask = isMasked | |
return newBubble | |
// let result = VHBubble() | |
// result.hasLayerMask = isMasked | |
// return result | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment