Swiftgram/TelegramUI/ChatTextInputPanelNode.swift
2016-11-22 21:31:10 +03:00

638 lines
31 KiB
Swift

import Foundation
import UIKit
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
private let textInputViewBackground: UIImage = {
let diameter: CGFloat = 35.0
UIGraphicsBeginImageContextWithOptions(CGSize(width: diameter, height: diameter), true, 0.0)
let context = UIGraphicsGetCurrentContext()!
context.setFillColor(UIColor(0xF5F6F8).cgColor)
context.fill(CGRect(x: 0.0, y: 0.0, width: diameter, height: diameter))
context.setFillColor(UIColor.white.cgColor)
context.fillEllipse(in: CGRect(x: 0.0, y: 0.0, width: diameter, height: diameter))
context.setStrokeColor(UIColor(0xC9CDD1).cgColor)
let strokeWidth: CGFloat = 0.5
context.setLineWidth(strokeWidth)
context.strokeEllipse(in: CGRect(x: strokeWidth / 2.0, y: strokeWidth / 2.0, width: diameter - strokeWidth, height: diameter - strokeWidth))
let image = UIGraphicsGetImageFromCurrentImageContext()!.stretchableImage(withLeftCapWidth: Int(diameter / 2.0), topCapHeight: Int(diameter / 2.0))
UIGraphicsEndImageContext()
return image
}()
private let searchLayoutClearButtonImage = generateImage(CGSize(width: 14.0, height: 14.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor(0x9099A2, 0.6).cgColor)
context.setStrokeColor(UIColor.white.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setLineWidth(1.5)
context.setLineCap(.round)
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.rotate(by: CGFloat(M_PI / 4))
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
let lineHeight: CGFloat = 7.0
context.beginPath()
context.move(to: CGPoint(x: size.width / 2.0, y: (size.width - lineHeight) / 2.0))
context.addLine(to: CGPoint(x: size.width / 2.0, y: (size.width - lineHeight) / 2.0 + lineHeight))
context.strokePath()
context.beginPath()
context.move(to: CGPoint(x: (size.width - lineHeight) / 2.0, y: size.width / 2.0))
context.addLine(to: CGPoint(x: (size.width - lineHeight) / 2.0 + lineHeight, y: size.width / 2.0))
context.strokePath()
})
private let searchLayoutProgressImage = generateImage(CGSize(width: 22.0, height: 22.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(UIColor(0x9099A2, 0.6).cgColor)
let lineWidth: CGFloat = 2.0
let cutoutWidth: CGFloat = 4.0
context.setLineWidth(lineWidth)
context.strokeEllipse(in: CGRect(origin: CGPoint(x: lineWidth / 2.0, y: lineWidth / 2.0), size: CGSize(width: size.width - lineWidth, height: size.height - lineWidth)))
context.clear(CGRect(origin: CGPoint(x: (size.width - cutoutWidth) / 2.0, y: 0.0), size: CGSize(width: cutoutWidth, height: size.height / 2.0)))
})
private let attachmentIcon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconAttachment"), color: UIColor(0x9099A2))
private let micIcon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconMicrophone"), color: UIColor(0x9099A2))
private let sendIcon = UIImage(bundleImageName: "Chat/Input/Text/IconSend")?.precomposed()
enum ChatTextInputAccessoryItem {
case keyboard
case stickers
case inputButtons
}
struct ChatTextInputPanelState: Equatable {
let accessoryItems: [ChatTextInputAccessoryItem]
init(accessoryItems: [ChatTextInputAccessoryItem]) {
self.accessoryItems = accessoryItems
}
init() {
self.accessoryItems = []
}
static func ==(lhs: ChatTextInputPanelState, rhs: ChatTextInputPanelState) -> Bool {
if lhs.accessoryItems != rhs.accessoryItems {
return false
}
return true
}
}
private let keyboardImage = UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconKeyboard")?.precomposed()
private let stickersImage = UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconStickers")?.precomposed()
private let inputButtonsImage = UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconInputButtons")?.precomposed()
private final class AccessoryItemIconButton: HighlightableButton {
init(item: ChatTextInputAccessoryItem) {
super.init(frame: CGRect())
switch item {
case .keyboard:
self.setImage(keyboardImage, for: [])
case .stickers:
self.setImage(stickersImage, for: [])
case .inputButtons:
self.setImage(inputButtonsImage, for: [])
}
//self.backgroundColor = UIColor.lightGray.withAlphaComponent(0.5)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var buttonWidth: CGFloat {
return (self.image(for: [])?.size.width ?? 0.0) + CGFloat(8.0)
}
}
class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
var textPlaceholderNode: TextNode
var textInputNode: ASEditableTextNode?
let textInputBackgroundView: UIImageView
let micButton: HighlightableButton
let sendButton: HighlightableButton
let attachmentButton: HighlightableButton
let searchLayoutClearButton: HighlightableButton
let searchLayoutProgressView: UIImageView
private var accessoryItemButtons: [(ChatTextInputAccessoryItem, AccessoryItemIconButton)] = []
var displayAttachmentMenu: () -> Void = { }
var sendMessage: () -> Void = { }
var updateHeight: () -> Void = { }
private var updatingInputState = false
private var currentPlaceholder: String?
private var presentationInterfaceState = ChatPresentationInterfaceState()
private var keepSendButtonEnabled = false
private var extendedSearchLayout = false
var inputTextState: ChatTextInputState {
if let textInputNode = self.textInputNode {
let text = textInputNode.attributedText?.string ?? ""
let selectionRange: Range<Int> = textInputNode.selectedRange.location ..< (textInputNode.selectedRange.location + textInputNode.selectedRange.length)
return ChatTextInputState(inputText: text, selectionRange: selectionRange)
} else {
return ChatTextInputState()
}
}
func updateInputTextState(_ state: ChatTextInputState, keepSendButtonEnabled: Bool, extendedSearchLayout: Bool, animated: Bool) {
if !state.inputText.isEmpty && self.textInputNode == nil {
self.loadTextInputNode()
}
if let textInputNode = self.textInputNode {
self.updatingInputState = true
textInputNode.attributedText = NSAttributedString(string: state.inputText, font: Font.regular(17.0), textColor: UIColor.black)
textInputNode.selectedRange = NSMakeRange(state.selectionRange.lowerBound, state.selectionRange.count)
self.updatingInputState = false
self.keepSendButtonEnabled = keepSendButtonEnabled
self.extendedSearchLayout = extendedSearchLayout
self.updateTextNodeText(animated: animated)
}
}
func updateKeepSendButtonEnabled(keepSendButtonEnabled: Bool, extendedSearchLayout: Bool, animated: Bool) {
if keepSendButtonEnabled != self.keepSendButtonEnabled || extendedSearchLayout != self.extendedSearchLayout {
self.keepSendButtonEnabled = keepSendButtonEnabled
self.extendedSearchLayout = extendedSearchLayout
self.updateTextNodeText(animated: animated)
}
}
var text: String {
get {
return self.textInputNode?.attributedText?.string ?? ""
} set(value) {
if let textInputNode = self.textInputNode {
textInputNode.attributedText = NSAttributedString(string: value, font: Font.regular(17.0), textColor: UIColor.black)
self.editableTextNodeDidUpdateText(textInputNode)
}
}
}
let textFieldInsets = UIEdgeInsets(top: 6.0, left: 42.0, bottom: 6.0, right: 42.0)
let textInputViewInternalInsets = UIEdgeInsets(top: 6.5, left: 13.0, bottom: 7.5, right: 13.0)
let accessoryButtonSpacing: CGFloat = 0.0
let accessoryButtonInset: CGFloat = 4.0 + UIScreenPixel
override init() {
self.textInputBackgroundView = UIImageView(image: textInputViewBackground)
self.textPlaceholderNode = TextNode()
self.textPlaceholderNode.isLayerBacked = true
self.attachmentButton = HighlightableButton()
self.searchLayoutClearButton = HighlightableButton()
self.searchLayoutProgressView = UIImageView(image: searchLayoutProgressImage)
self.searchLayoutProgressView.isHidden = true
self.micButton = HighlightableButton()
self.sendButton = HighlightableButton()
super.init()
self.attachmentButton.setImage(attachmentIcon, for: [])
self.attachmentButton.addTarget(self, action: #selector(self.attachmentButtonPressed), for: .touchUpInside)
self.view.addSubview(self.attachmentButton)
self.micButton.setImage(micIcon, for: [])
self.micButton.addTarget(self, action: #selector(self.micButtonPressed), for: .touchUpInside)
self.view.addSubview(self.micButton)
self.sendButton.setImage(sendIcon, for: [])
self.sendButton.addTarget(self, action: #selector(self.sendButtonPressed), for: .touchUpInside)
self.sendButton.alpha = 0.0
self.view.addSubview(self.sendButton)
self.searchLayoutClearButton.setImage(searchLayoutClearButtonImage, for: [])
self.searchLayoutClearButton.addTarget(self, action: #selector(self.searchLayoutClearButtonPressed), for: .touchUpInside)
self.searchLayoutClearButton.alpha = 0.0
self.searchLayoutClearButton.addSubview(self.searchLayoutProgressView)
self.view.addSubview(self.textInputBackgroundView)
let placeholderLayout = TextNode.asyncLayout(self.textPlaceholderNode)
let (placeholderSize, placeholderApply) = placeholderLayout(NSAttributedString(string: "Message", font: Font.regular(17.0), textColor: UIColor(0xC8C8CE)), nil, 1, .end, CGSize(width: 320.0, height: CGFloat.greatestFiniteMagnitude), nil)
self.textPlaceholderNode.frame = CGRect(origin: CGPoint(), size: placeholderSize.size)
let _ = placeholderApply()
self.addSubnode(self.textPlaceholderNode)
self.view.addSubview(self.searchLayoutClearButton)
self.textInputBackgroundView.clipsToBounds = true
let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:)))
recognizer.touchDown = { [weak self] in
if let strongSelf = self {
strongSelf.ensureFocused()
}
}
self.textInputBackgroundView.addGestureRecognizer(recognizer)
self.textInputBackgroundView.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func loadTextInputNode() {
let textInputNode = ASEditableTextNode()
textInputNode.typingAttributes = [NSFontAttributeName: Font.regular(17.0)]
textInputNode.clipsToBounds = true
textInputNode.delegate = self
textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
self.addSubnode(textInputNode)
self.textInputNode = textInputNode
textInputNode.frame = CGRect(x: self.textFieldInsets.left + self.textInputViewInternalInsets.left, y: self.textFieldInsets.top + self.textInputViewInternalInsets.top, width: self.frame.size.width - self.textFieldInsets.left - self.textFieldInsets.right - self.textInputViewInternalInsets.left - self.textInputViewInternalInsets.right, height: self.frame.size.height - self.textFieldInsets.top - self.textFieldInsets.bottom - self.textInputViewInternalInsets.top - self.textInputViewInternalInsets.bottom)
self.textInputBackgroundView.isUserInteractionEnabled = false
self.textInputBackgroundView.removeGestureRecognizer(self.textInputBackgroundView.gestureRecognizers![0])
let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:)))
recognizer.touchDown = { [weak self] in
if let strongSelf = self {
strongSelf.ensureFocused()
}
}
textInputNode.view.addGestureRecognizer(recognizer)
}
private func calculateTextFieldMetrics(width: CGFloat) -> (accessoryButtonsWidth: CGFloat, textFieldHeight: CGFloat) {
let accessoryButtonInset = self.accessoryButtonInset
let accessoryButtonSpacing = self.accessoryButtonSpacing
var accessoryButtonsWidth: CGFloat = 0.0
var firstButton = true
for (_, button) in self.accessoryItemButtons {
if firstButton {
firstButton = false
accessoryButtonsWidth += accessoryButtonInset
} else {
accessoryButtonsWidth += accessoryButtonSpacing
}
accessoryButtonsWidth += button.buttonWidth
}
let textFieldHeight: CGFloat
if let textInputNode = self.textInputNode {
textFieldHeight = min(115.0, max(21.0, ceil(textInputNode.measure(CGSize(width: width - self.textFieldInsets.left - self.textFieldInsets.right - self.textInputViewInternalInsets.left - self.textInputViewInternalInsets.right - accessoryButtonsWidth, height: CGFloat.greatestFiniteMagnitude)).height)))
} else {
textFieldHeight = 21.0
}
return (accessoryButtonsWidth, textFieldHeight)
}
private func panelHeight(textFieldHeight: CGFloat) -> CGFloat {
return textFieldHeight + self.textFieldInsets.top + self.textFieldInsets.bottom + self.textInputViewInternalInsets.top + self.textInputViewInternalInsets.bottom
}
override func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat {
if self.presentationInterfaceState != interfaceState {
let previousState = self.presentationInterfaceState
self.presentationInterfaceState = interfaceState
if let peer = interfaceState.peer, previousState.peer == nil || !peer.isEqual(previousState.peer!) {
let placeholder: String
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
placeholder = "Broadcast"
} else {
placeholder = "Message"
}
if self.currentPlaceholder != placeholder {
self.currentPlaceholder = placeholder
let placeholderLayout = TextNode.asyncLayout(self.textPlaceholderNode)
let (placeholderSize, placeholderApply) = placeholderLayout(NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: UIColor(0xbebec0)), nil, 1, .end, CGSize(width: 320.0, height: CGFloat.greatestFiniteMagnitude), nil)
self.textPlaceholderNode.frame = CGRect(origin: self.textPlaceholderNode.frame.origin, size: placeholderSize.size)
let _ = placeholderApply()
}
}
}
let minimalHeight: CGFloat = 47.0
let minimalInputHeight: CGFloat = 35.0
var animatedTransition = true
if case .immediate = transition {
animatedTransition = false
}
var updateAccessoryButtons = false
if self.presentationInterfaceState.inputTextPanelState.accessoryItems.count == self.accessoryItemButtons.count {
for i in 0 ..< self.presentationInterfaceState.inputTextPanelState.accessoryItems.count {
if self.presentationInterfaceState.inputTextPanelState.accessoryItems[i] != self.accessoryItemButtons[i].0 {
updateAccessoryButtons = true
break
}
}
} else {
updateAccessoryButtons = true
}
var removeAccessoryButtons: [AccessoryItemIconButton]?
if updateAccessoryButtons {
var updatedButtons: [(ChatTextInputAccessoryItem, AccessoryItemIconButton)] = []
for item in self.presentationInterfaceState.inputTextPanelState.accessoryItems {
var itemAndButton: (ChatTextInputAccessoryItem, AccessoryItemIconButton)?
for i in 0 ..< self.accessoryItemButtons.count {
if self.accessoryItemButtons[i].0 == item {
itemAndButton = self.accessoryItemButtons[i]
self.accessoryItemButtons.remove(at: i)
break
}
}
if itemAndButton == nil {
let button = AccessoryItemIconButton(item: item)
button.addTarget(self, action: #selector(self.accessoryItemButtonPressed(_:)), for: [.touchUpInside])
itemAndButton = (item, button)
}
updatedButtons.append(itemAndButton!)
}
for (_, button) in self.accessoryItemButtons {
if animatedTransition {
if removeAccessoryButtons == nil {
removeAccessoryButtons = []
}
removeAccessoryButtons!.append(button)
} else {
button.removeFromSuperview()
}
}
self.accessoryItemButtons = updatedButtons
}
let (accessoryButtonsWidth, textFieldHeight) = self.calculateTextFieldMetrics(width: width)
let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight)
transition.updateFrame(layer: self.attachmentButton.layer, frame: CGRect(origin: CGPoint(x: 2.0 - UIScreenPixel, y: panelHeight - minimalHeight), size: CGSize(width: 40.0, height: minimalHeight)))
var composeButtonsOffset: CGFloat = 0.0
var textInputBackgroundWidthOffset: CGFloat = 0.0
if self.extendedSearchLayout {
composeButtonsOffset = 44.0
textInputBackgroundWidthOffset = 36.0
}
transition.updateFrame(layer: self.micButton.layer, frame: CGRect(origin: CGPoint(x: width - 43.0 - UIScreenPixel + composeButtonsOffset, y: panelHeight - minimalHeight - UIScreenPixel), size: CGSize(width: 44.0, height: minimalHeight)))
transition.updateFrame(layer: self.sendButton.layer, frame: CGRect(origin: CGPoint(x: width - 43.0 - UIScreenPixel + composeButtonsOffset, y: panelHeight - minimalHeight - UIScreenPixel), size: CGSize(width: 44.0, height: minimalHeight)))
let searchLayoutClearButtonSize = CGSize(width: 44.0, height: minimalHeight)
transition.updateFrame(layer: self.searchLayoutClearButton.layer, frame: CGRect(origin: CGPoint(x: width - self.textFieldInsets.left - self.textFieldInsets.right + textInputBackgroundWidthOffset + 3.0, y: panelHeight - minimalHeight), size: searchLayoutClearButtonSize))
let searchProgressSize = self.searchLayoutProgressView.bounds.size
transition.updateFrame(layer: self.searchLayoutProgressView.layer, frame: CGRect(origin: CGPoint(x: floor((searchLayoutClearButtonSize.width - searchProgressSize.width) / 2.0), y: floor((searchLayoutClearButtonSize.height - searchProgressSize.height) / 2.0)), size: searchProgressSize))
if let textInputNode = self.textInputNode {
transition.updateFrame(node: textInputNode, frame: CGRect(x: self.textFieldInsets.left + self.textInputViewInternalInsets.left, y: self.textFieldInsets.top + self.textInputViewInternalInsets.top, width: width - self.textFieldInsets.left - self.textFieldInsets.right - self.textInputViewInternalInsets.left - self.textInputViewInternalInsets.right - accessoryButtonsWidth, height: panelHeight - self.textFieldInsets.top - self.textFieldInsets.bottom - self.textInputViewInternalInsets.top - self.textInputViewInternalInsets.bottom))
}
transition.updateFrame(node: self.textPlaceholderNode, frame: CGRect(origin: CGPoint(x: self.textFieldInsets.left + self.textInputViewInternalInsets.left, y: self.textFieldInsets.top + self.textInputViewInternalInsets.top + 0.5), size: self.textPlaceholderNode.frame.size))
transition.updateFrame(layer: self.textInputBackgroundView.layer, frame: CGRect(x: self.textFieldInsets.left, y: self.textFieldInsets.top, width: width - self.textFieldInsets.left - self.textFieldInsets.right + textInputBackgroundWidthOffset, height: panelHeight - self.textFieldInsets.top - self.textFieldInsets.bottom))
var nextButtonTopRight = CGPoint(x: width - self.textFieldInsets.right - accessoryButtonInset, y: panelHeight - self.textFieldInsets.bottom - minimalInputHeight)
for (_, button) in self.accessoryItemButtons.reversed() {
let buttonSize = CGSize(width: button.buttonWidth, height: minimalInputHeight)
let buttonFrame = CGRect(origin: CGPoint(x: nextButtonTopRight.x - buttonSize.width, y: nextButtonTopRight.y + floor((minimalInputHeight - buttonSize.height) / 2.0)), size: buttonSize)
if button.superview == nil {
self.view.addSubview(button)
button.frame = buttonFrame
transition.updateFrame(layer: button.layer, frame: buttonFrame)
if animatedTransition {
button.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
button.layer.animateScale(from: 0.2, to: 1.0, duration: 0.25)
}
} else {
transition.updateFrame(layer: button.layer, frame: buttonFrame)
}
nextButtonTopRight.x -= buttonSize.width
nextButtonTopRight.x -= accessoryButtonSpacing
}
if let removeAccessoryButtons = removeAccessoryButtons {
for button in removeAccessoryButtons {
let buttonFrame = CGRect(origin: CGPoint(x: button.frame.origin.x, y: panelHeight - self.textFieldInsets.bottom - minimalInputHeight), size: button.frame.size)
transition.updateFrame(layer: button.layer, frame: buttonFrame)
button.layer.animateScale(from: 1.0, to: 0.2, duration: 0.25, removeOnCompletion: false)
button.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak button] _ in
button?.removeFromSuperview()
})
}
}
return panelHeight
}
@objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
if let textInputNode = self.textInputNode {
let inputTextState = self.inputTextState
self.interfaceInteraction?.updateTextInputState({ _ in return inputTextState })
self.updateTextNodeText(animated: true)
}
}
private func updateTextNodeText(animated: Bool) {
var hasText = false
if let textInputNode = self.textInputNode, let attributedText = textInputNode.attributedText, attributedText.length != 0 {
hasText = true
}
self.textPlaceholderNode.isHidden = hasText
if self.extendedSearchLayout {
if !self.sendButton.alpha.isZero {
self.sendButton.alpha = 0.0
if animated {
self.sendButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
self.sendButton.layer.animateScale(from: 1.0, to: 0.2, duration: 0.2)
}
}
if !self.micButton.alpha.isZero {
self.micButton.alpha = 0.0
if animated {
self.micButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
self.micButton.layer.animateScale(from: 1.0, to: 0.2, duration: 0.2)
}
}
if self.searchLayoutClearButton.alpha.isZero {
self.searchLayoutClearButton.alpha = 1.0
if animated {
self.searchLayoutClearButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
self.searchLayoutClearButton.layer.animateScale(from: 0.8, to: 1.0, duration: 0.2)
}
}
} else {
var animateWithBounce = true
if !self.searchLayoutClearButton.alpha.isZero {
animateWithBounce = false
self.searchLayoutClearButton.alpha = 0.0
if animated {
self.searchLayoutClearButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
self.searchLayoutClearButton.layer.animateScale(from: 1.0, to: 0.8, duration: 0.2)
}
}
if hasText || self.keepSendButtonEnabled {
if self.sendButton.alpha.isZero {
self.sendButton.alpha = 1.0
if animated {
self.sendButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
if animateWithBounce {
self.sendButton.layer.animateSpring(from: NSNumber(value: Float(0.1)), to: NSNumber(value: Float(1.0)), keyPath: "transform.scale", duration: 0.6)
} else {
self.sendButton.layer.animateScale(from: 0.2, to: 1.0, duration: 0.25)
}
}
}
if !self.micButton.alpha.isZero {
self.micButton.alpha = 0.0
if animated {
self.micButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
}
}
} else {
if self.micButton.alpha.isZero {
self.micButton.alpha = 1.0
if animated {
self.micButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
if animateWithBounce {
self.micButton.layer.animateSpring(from: NSNumber(value: Float(0.1)), to: NSNumber(value: Float(1.0)), keyPath: "transform.scale", duration: 0.6)
} else {
self.micButton.layer.animateScale(from: 0.2, to: 1.0, duration: 0.25)
}
}
}
if !self.sendButton.alpha.isZero {
self.sendButton.alpha = 0.0
if animated {
self.sendButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
}
}
}
}
let (accessoryButtonsWidth, textFieldHeight) = self.calculateTextFieldMetrics(width: self.bounds.size.width)
let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight)
if !self.bounds.size.height.isEqual(to: panelHeight) {
self.updateHeight()
}
}
@objc func editableTextNodeDidChangeSelection(_ editableTextNode: ASEditableTextNode, fromSelectedRange: NSRange, toSelectedRange: NSRange, dueToEditing: Bool) {
if !dueToEditing && !updatingInputState {
let inputTextState = self.inputTextState
self.interfaceInteraction?.updateTextInputState({ _ in return inputTextState })
}
}
@objc func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {
self.interfaceInteraction?.updateInputMode({ _ in .text })
}
@objc func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) {
/*self.interfaceInteraction?.updateInputMode({ mode in
if case .text = mode {
return .none
} else {
return mode
}
})*/
}
@objc func sendButtonPressed() {
let text = self.textInputNode?.attributedText?.string ?? ""
self.sendMessage()
}
@objc func attachmentButtonPressed() {
self.displayAttachmentMenu()
}
@objc func searchLayoutClearButtonPressed() {
if let interfaceInteraction = self.interfaceInteraction {
interfaceInteraction.updateTextInputState { textInputState in
if let (range, type, queryRange) = textInputStateContextQueryRangeAndType(textInputState), type == [.contextRequest] {
if let queryRange = queryRange, !queryRange.isEmpty {
var inputText = textInputState.inputText
inputText.replaceSubrange(queryRange, with: "")
return ChatTextInputState(inputText: inputText)
} else {
return ChatTextInputState(inputText: "")
}
}
return textInputState
}
}
}
@objc func micButtonPressed() {
}
@objc func textInputBackgroundViewTap(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.ensureFocused()
}
}
var isFocused: Bool {
return self.textInputNode?.isFirstResponder() ?? false
}
func ensureUnfocused() {
self.textInputNode?.resignFirstResponder()
}
func ensureFocused() {
if self.textInputNode == nil {
self.loadTextInputNode()
}
self.textInputNode?.becomeFirstResponder()
}
func animateTextSend() {
/*if let textInputNode = self.textInputNode {
let snapshot = textInputNode.view.snapshotViewAfterScreenUpdates(false)
snapshot.frame = self.textInputBackgroundView.convertRect(textInputNode.view.bounds, fromView: textInputNode.view)
self.textInputBackgroundView.addSubview(snapshot)
UIView.animateWithDuration(0.3, animations: {
snapshot.alpha = 0.0
snapshot.transform = CGAffineTransformMakeTranslation(0.0, -20.0)
}, completion: { _ in
snapshot.removeFromSuperview()
})
}*/
}
@objc func accessoryItemButtonPressed(_ button: UIView) {
for (item, currentButton) in self.accessoryItemButtons {
if currentButton === button {
switch item {
case .inputButtons:
break
case .stickers:
self.interfaceInteraction?.updateInputMode({ _ in .media })
case .keyboard:
self.interfaceInteraction?.updateInputMode({ _ in .text })
}
break
}
}
}
}