From eaf0b74f1ba6d2412eb1b61a75af6466f1197dbd Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 15 Jul 2022 14:49:48 +0200 Subject: [PATCH] Emoji input and display in media selection --- submodules/AttachmentTextInputPanelNode/BUILD | 5 + .../AttachmentTextInputPanelNode.swift | 332 +++++++++++-- .../Sources/AttachmentController.swift | 13 +- .../Sources/AttachmentPanel.swift | 8 +- .../Sources/PagerComponent.swift | 6 +- submodules/GalleryUI/BUILD | 3 + .../ChatItemGalleryFooterContentNode.swift | 28 +- .../Sources/EntityKeyboard.swift | 7 +- .../EntityKeyboardBottomPanelComponent.swift | 95 ++-- ...tyKeyboardTopContainerPanelComponent.swift | 45 +- .../EntitySearchContentComponent.swift | 42 +- .../Sources/TextNodeWithEntities.swift | 2 +- .../TelegramUI/Sources/ChatController.swift | 15 +- .../Sources/ChatControllerNode.swift | 3 +- .../Sources/ChatEntityKeyboardInputNode.swift | 466 +++++++++++++----- .../Sources/PeerSelectionControllerNode.swift | 4 +- .../Sources/ReplyAccessoryPanelNode.swift | 1 + .../WebUI/Sources/WebAppController.swift | 4 +- 18 files changed, 829 insertions(+), 250 deletions(-) diff --git a/submodules/AttachmentTextInputPanelNode/BUILD b/submodules/AttachmentTextInputPanelNode/BUILD index 0c9fa47d61..f82aeb9a52 100644 --- a/submodules/AttachmentTextInputPanelNode/BUILD +++ b/submodules/AttachmentTextInputPanelNode/BUILD @@ -29,6 +29,11 @@ swift_library( "//submodules/Pasteboard:Pasteboard", "//submodules/ContextUI:ContextUI", "//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView", + "//submodules/ComponentFlow:ComponentFlow", + "//submodules/Components/LottieAnimationComponent:LottieAnimationComponent", + "//submodules/TelegramUI/Components/AnimationCache:AnimationCache", + "//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer", + "//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities", ], visibility = [ "//visibility:public", diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift index d747d0be7d..f39df3f87a 100644 --- a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift @@ -19,6 +19,11 @@ import TextInputMenu import ChatPresentationInterfaceState import Pasteboard import EmojiTextAttachmentView +import ComponentFlow +import LottieAnimationComponent +import AnimationCache +import MultiAnimationRenderer +import TextNodeWithEntities private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers]) private let minInputFontSize: CGFloat = 5.0 @@ -64,7 +69,7 @@ private func calculateTextFieldRealInsets(_ presentationInterfaceState: ChatPres top = 0.0 bottom = 0.0 } - return UIEdgeInsets(top: 4.5 + top, left: 0.0, bottom: 5.5 + bottom, right: 0.0) + return UIEdgeInsets(top: 4.5 + top, left: 0.0, bottom: 5.5 + bottom, right: 32.0) } private var currentTextInputBackgroundImage: (UIColor, UIColor, CGFloat, UIImage)? @@ -108,33 +113,6 @@ private func textInputBackgroundImage(backgroundColor: UIColor?, inputBackground } } -private final class EntityInputView: UIInputView, UIInputViewAudioFeedback { - override var inputViewStyle: UIInputView.Style { - get { - return .default - } - } - override var allowsSelfSizing: Bool { - get { - return true - } - set { - } - } - - override var intrinsicContentSize: CGSize { - CGSize(width: UIView.noIntrinsicMetric, height: 300) - } - - override func sizeToFit() { - print("sizeToFit") - } - - override func sizeThatFits(_ size: CGSize) -> CGSize { - return CGSize(width: size.width, height: 100.0) - } -} - private class CaptionEditableTextNode: EditableTextNode { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let previousAlpha = self.alpha @@ -145,18 +123,91 @@ private class CaptionEditableTextNode: EditableTextNode { } } +public protocol AttachmentTextInputPanelInputView: UIView { + var insertText: ((NSAttributedString) -> Void)? { get set } + var deleteBackwards: (() -> Void)? { get set } + var switchToKeyboard: (() -> Void)? { get set } + var presentController: ((ViewController) -> Void)? { get set } +} + +final class CustomEmojiContainerView: UIView { + private let emojiViewProvider: (ChatTextInputTextCustomEmojiAttribute) -> UIView? + + private var emojiLayers: [InlineStickerItemLayer.Key: UIView] = [:] + + init(emojiViewProvider: @escaping (ChatTextInputTextCustomEmojiAttribute) -> UIView?) { + self.emojiViewProvider = emojiViewProvider + + super.init(frame: CGRect()) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + func update(emojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)]) { + var nextIndexById: [Int64: Int] = [:] + + var validKeys = Set() + for (rect, emoji) in emojiRects { + let index: Int + if let nextIndex = nextIndexById[emoji.fileId] { + index = nextIndex + } else { + index = 0 + } + nextIndexById[emoji.fileId] = index + 1 + + let key = InlineStickerItemLayer.Key(id: emoji.fileId, index: index) + + let view: UIView + if let current = self.emojiLayers[key] { + view = current + } else if let newView = self.emojiViewProvider(emoji) { + view = newView + self.addSubview(newView) + self.emojiLayers[key] = view + } else { + continue + } + + let size = CGSize(width: 24.0, height: 24.0) + + view.frame = CGRect(origin: CGPoint(x: floor(rect.midX - size.width / 2.0), y: floor(rect.midY - size.height / 2.0)), size: size) + + validKeys.insert(key) + } + + var removeKeys: [InlineStickerItemLayer.Key] = [] + for (key, view) in self.emojiLayers { + if !validKeys.contains(key) { + removeKeys.append(key) + view.removeFromSuperview() + } + } + for key in removeKeys { + self.emojiLayers.removeValue(forKey: key) + } + } +} + public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, ASEditableTextNodeDelegate { private let context: AccountContext private let isCaption: Bool private let isAttachment: Bool + private let presentController: (ViewController) -> Void + private let makeEntityInputView: () -> AttachmentTextInputPanelInputView? + private var textPlaceholderNode: ImmediateTextNode private let textInputContainerBackgroundNode: ASImageNode private let textInputContainer: ASDisplayNode public var textInputNode: EditableTextNode? private var dustNode: InvisibleInkDustNode? - private var oneLineNode: ImmediateTextNode + private var customEmojiContainerView: CustomEmojiContainerView? + private var oneLineNode: TextNodeWithEntities + private var oneLineNodeAttributedText: NSAttributedString? private var oneLineDustNode: InvisibleInkDustNode? let textInputBackgroundNode: ASDisplayNode @@ -164,6 +215,8 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS private var transparentTextInputBackgroundImage: UIImage? private let actionButtons: AttachmentTextInputActionButtonsNode private let counterTextNode: ImmediateTextNode + + private let inputModeView: ComponentHostView private var validLayout: (CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, LayoutMetrics, Bool)? @@ -270,14 +323,23 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS private var spoilersRevealed = false private var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)? + private let animationCache: AnimationCache + private let animationRenderer: MultiAnimationRenderer private var maxCaptionLength: Int32? - public init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, isCaption: Bool = false, isAttachment: Bool = false, presentController: @escaping (ViewController) -> Void) { + public init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, isCaption: Bool = false, isAttachment: Bool = false, presentController: @escaping (ViewController) -> Void, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { self.context = context self.presentationInterfaceState = presentationInterfaceState self.isCaption = isCaption self.isAttachment = isAttachment + self.presentController = presentController + self.makeEntityInputView = makeEntityInputView + + self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: { + return TempBox.shared.tempFile(fileName: "file").path + }) + self.animationRenderer = MultiAnimationRendererImpl() var hasSpoilers = true if presentationInterfaceState.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat { @@ -293,6 +355,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS if !isCaption { self.textInputContainer.addSubnode(self.textInputContainerBackgroundNode) } + + self.inputModeView = ComponentHostView() + self.textInputContainer.view.addSubview(self.inputModeView) self.textInputContainer.clipsToBounds = true self.textInputBackgroundNode = ASDisplayNode() @@ -303,9 +368,8 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS self.textPlaceholderNode.maximumNumberOfLines = 1 self.textPlaceholderNode.isUserInteractionEnabled = false - self.oneLineNode = ImmediateTextNode() - self.oneLineNode.maximumNumberOfLines = 1 - self.oneLineNode.isUserInteractionEnabled = false + self.oneLineNode = TextNodeWithEntities() + self.oneLineNode.textNode.isUserInteractionEnabled = false self.actionButtons = AttachmentTextInputActionButtonsNode(presentationInterfaceState: presentationInterfaceState, presentController: presentController) self.counterTextNode = ImmediateTextNode() @@ -331,7 +395,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS self.addSubnode(self.counterTextNode) if isCaption { - self.addSubnode(self.oneLineNode) + self.addSubnode(self.oneLineNode.textNode) } self.textInputBackgroundImageNode.clipsToBounds = true @@ -343,13 +407,13 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } self.textInputBackgroundNode.view.addGestureRecognizer(recognizer) - /*self.emojiViewProvider = { [weak self] emoji in - guard let strongSelf = self, let file = strongSelf.context.animatedEmojiStickers[emoji]?.first?.file else { + self.emojiViewProvider = { [weak self] emoji in + guard let strongSelf = self, let presentationInterfaceState = strongSelf.presentationInterfaceState else { return UIView() } - return EmojiTextAttachmentView(context: context, file: file) - }*/ + return EmojiTextAttachmentView(context: context, emoji: emoji, file: emoji.file, cache: strongSelf.animationCache, renderer: strongSelf.animationRenderer, placeholderColor: presentationInterfaceState.theme.chat.inputPanel.inputTextColor.withAlphaComponent(0.12)) + } self.updateSendButtonEnabled(isCaption || isAttachment, animated: false) @@ -464,11 +528,6 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS textInputNode.view.addGestureRecognizer(recognizer) textInputNode.textView.accessibilityHint = self.textPlaceholderNode.attributedText?.string - - /*let entityInputView = EntityInputView() - entityInputView.frame = CGRect(origin: CGPoint(), size: CGSize(width: 100.0, height: 100.0)) - entityInputView.backgroundColor = .blue - textInputNode.textView.inputView = entityInputView*/ } private func textFieldMaxHeight(_ maxHeight: CGFloat, metrics: LayoutMetrics) -> CGFloat { @@ -528,6 +587,14 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS return minimalHeight } + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if !self.inputModeView.isHidden, let result = self.inputModeView.hitTest(self.view.convert(point, to: self.inputModeView), with: event) { + return result + } + + return super.hitTest(point, with: event) + } + public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { let hadLayout = self.validLayout != nil let previousAdditionalSideInsets = self.validLayout?.3 @@ -688,7 +755,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS if self.isCaption { if self.isFocused { - self.oneLineNode.alpha = 0.0 + self.oneLineNode.textNode.alpha = 0.0 self.oneLineDustNode?.alpha = 0.0 self.textInputNode?.alpha = 1.0 @@ -698,7 +765,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } else { panelHeight = minimalHeight - transition.updateAlpha(node: self.oneLineNode, alpha: inputHasText ? 1.0 : 0.0) + transition.updateAlpha(node: self.oneLineNode.textNode, alpha: inputHasText ? 1.0 : 0.0) if let oneLineDustNode = self.oneLineDustNode { transition.updateAlpha(node: oneLineDustNode, alpha: inputHasText ? 1.0 : 0.0) } @@ -711,9 +778,34 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS transition.updateAlpha(node: self.textInputBackgroundImageNode, alpha: inputHasText ? 1.0 : 0.0) } - let oneLineSize = self.oneLineNode.updateLayout(CGSize(width: baseWidth - textFieldInsets.left - textFieldInsets.right, height: CGFloat.greatestFiniteMagnitude)) - let oneLineFrame = CGRect(origin: CGPoint(x: leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: oneLineSize) - self.oneLineNode.frame = oneLineFrame + let makeOneLineLayout = TextNodeWithEntities.asyncLayout(self.oneLineNode) + let (oneLineLayout, oneLineApply) = makeOneLineLayout(TextNodeLayoutArguments( + attributedString: self.oneLineNodeAttributedText, + backgroundColor: nil, + minimumNumberOfLines: 1, + maximumNumberOfLines: 1, + truncationType: .end, + constrainedSize: CGSize(width: baseWidth - textFieldInsets.left - textFieldInsets.right, height: CGFloat.greatestFiniteMagnitude), + alignment: .left, + verticalAlignment: .top, + lineSpacing: 0.0, + cutout: nil, insets: UIEdgeInsets(), + lineColor: nil, + textShadowColor: nil, + textStroke: nil, + displaySpoilers: false, + displayEmbeddedItemsUnderSpoilers: false + )) + + let oneLineFrame = CGRect(origin: CGPoint(x: leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: oneLineLayout.size) + self.oneLineNode.textNode.frame = oneLineFrame + let _ = oneLineApply(TextNodeWithEntities.Arguments( + context: self.context, + cache: self.animationCache, + renderer: self.animationRenderer, + placeholderColor: self.presentationInterfaceState?.theme.chat.inputPanel.inputTextColor.withAlphaComponent(0.12) ?? .lightGray, + attemptSynchronous: false + )) self.updateOneLineSpoiler() } @@ -725,6 +817,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS if let textInputNode = self.textInputNode { let textFieldFrame = CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top), size: CGSize(width: textInputFrame.size.width - (self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right), height: textInputFrame.size.height - self.textInputViewInternalInsets.top - textInputViewInternalInsets.bottom)) let shouldUpdateLayout = textFieldFrame.size != textInputNode.frame.size + if let presentationInterfaceState = self.presentationInterfaceState { + textInputNode.textContainerInset = calculateTextFieldRealInsets(presentationInterfaceState) + } transition.updateFrame(node: textInputNode, frame: textFieldFrame) if shouldUpdateLayout { textInputNode.layout() @@ -788,6 +883,37 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS var textInputViewRealInsets = UIEdgeInsets() if let presentationInterfaceState = self.presentationInterfaceState { textInputViewRealInsets = calculateTextFieldRealInsets(presentationInterfaceState) + + var colors: [String: UIColor] = [:] + let colorKeys: [String] = [ + "Ellipse 33.Ellipse 33.Stroke 1", + "Ellipse 34.Ellipse 34.Stroke 1", + "Oval.Oval.Fill 1", + "Oval 2.Oval.Fill 1", + "Path 85.Path 85.Stroke 1" + ] + for colorKey in colorKeys { + colors[colorKey] = presentationInterfaceState.theme.chat.inputPanel.inputControlColor + } + let animationComponent = LottieAnimationComponent( + animation: LottieAnimationComponent.AnimationItem( + name: "anim_smiletosticker", + colors: colors, + mode: .animateTransitionFromPrevious + ), + size: CGSize(width: 32.0, height: 32.0) + ) + let inputNodeSize = self.inputModeView.update( + transition: .immediate, + component: AnyComponent(Button( + content: AnyComponent(animationComponent), + action: { [weak self] in + self?.toggleInputMode() + })), + environment: {}, + containerSize: CGSize(width: 32.0, height: 32.0) + ) + transition.updateFrame(view: self.inputModeView, frame: CGRect(origin: CGPoint(x: textInputBackgroundFrame.maxX - inputNodeSize.width - 1.0, y: textInputBackgroundFrame.maxY - inputNodeSize.height - 1.0), size: inputNodeSize)) } let placeholderFrame: CGRect @@ -836,6 +962,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS let textColor = presentationInterfaceState.theme.chat.inputPanel.inputTextColor var rects: [CGRect] = [] + var customEmojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)] = [] if let attributedText = textInputNode.attributedText { let beginning = textInputNode.textView.beginningOfDocument @@ -873,6 +1000,16 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS addSpoiler(startIndex: currentStartIndex, endIndex: endIndex) } } + + if let value = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute { + if let start = textInputNode.textView.position(from: beginning, offset: range.location), let end = textInputNode.textView.position(from: start, offset: range.length), let textRange = textInputNode.textView.textRange(from: start, to: end) { + let textRects = textInputNode.textView.selectionRects(for: textRange) + for textRect in textRects { + customEmojiRects.append((textRect.rect, value)) + break + } + } + } }) } @@ -893,6 +1030,28 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS dustNode.removeFromSupernode() self.dustNode = nil } + + if !customEmojiRects.isEmpty { + let customEmojiContainerView: CustomEmojiContainerView + if let current = self.customEmojiContainerView { + customEmojiContainerView = current + } else { + customEmojiContainerView = CustomEmojiContainerView(emojiViewProvider: { [weak self] emoji in + guard let strongSelf = self, let emojiViewProvider = strongSelf.emojiViewProvider else { + return nil + } + return emojiViewProvider(emoji) + }) + customEmojiContainerView.isUserInteractionEnabled = false + textInputNode.textView.addSubview(customEmojiContainerView) + self.customEmojiContainerView = customEmojiContainerView + } + + customEmojiContainerView.update(emojiRects: customEmojiRects) + } else if let customEmojiContainerView = self.customEmojiContainerView { + customEmojiContainerView.removeFromSuperview() + self.customEmojiContainerView = nil + } } private func updateSpoilersRevealed(animated: Bool = true) { @@ -1016,6 +1175,71 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } } + private func toggleInputMode() { + self.loadTextInputNodeIfNeeded() + + guard let textInputNode = self.textInputNode else { + return + } + + var shouldHaveInputView = false + if textInputNode.textView.isFirstResponder { + if textInputNode.textView.inputView == nil { + shouldHaveInputView = true + } + } else { + shouldHaveInputView = true + } + + if shouldHaveInputView { + let inputView = self.makeEntityInputView() + inputView?.insertText = { [weak self] text in + guard let strongSelf = self else { + return + } + + strongSelf.interfaceInteraction?.updateTextInputStateAndMode { textInputState, inputMode in + let inputText = NSMutableAttributedString(attributedString: textInputState.inputText) + + let range = textInputState.selectionRange + inputText.replaceCharacters(in: NSMakeRange(range.lowerBound, range.count), with: text) + + let selectionPosition = range.lowerBound + (text.string as NSString).length + + return (ChatTextInputState(inputText: inputText, selectionRange: selectionPosition ..< selectionPosition), inputMode) + } + } + inputView?.deleteBackwards = { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.textInputNode?.textView.deleteBackward() + } + inputView?.switchToKeyboard = { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.toggleInputMode() + } + inputView?.presentController = { [weak self] c in + guard let strongSelf = self else { + return + } + strongSelf.presentController(c) + } + + textInputNode.textView.inputView = inputView + } else { + textInputNode.textView.inputView = nil + } + + if textInputNode.textView.isFirstResponder { + textInputNode.textView.reloadInputViews() + } else { + textInputNode.textView.becomeFirstResponder() + } + } + private func updateTextNodeText(animated: Bool) { var inputHasText = false if let textInputNode = self.textInputNode, let attributedText = textInputNode.attributedText, attributedText.length != 0 { @@ -1037,12 +1261,12 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS let trimmedText = NSMutableAttributedString(attributedString: attributedText.attributedSubstring(from: NSMakeRange(0, range.location))) trimmedText.append(NSAttributedString(string: "\u{2026}", font: textFont, textColor: textColor)) - self.oneLineNode.attributedText = trimmedText + self.oneLineNodeAttributedText = trimmedText } else { - self.oneLineNode.attributedText = attributedText + self.oneLineNodeAttributedText = attributedText } } else { - self.oneLineNode.attributedText = nil + self.oneLineNodeAttributedText = nil } let panelHeight = self.updateTextHeight(animated: animated) @@ -1052,15 +1276,15 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } private func updateOneLineSpoiler() { - if let textLayout = self.oneLineNode.cachedLayout, !textLayout.spoilers.isEmpty { + if let textLayout = self.oneLineNode.textNode.cachedLayout, !textLayout.spoilers.isEmpty { if self.oneLineDustNode == nil { let oneLineDustNode = InvisibleInkDustNode(textNode: nil) self.oneLineDustNode = oneLineDustNode - self.oneLineNode.supernode?.insertSubnode(oneLineDustNode, aboveSubnode: self.oneLineNode) + self.oneLineNode.textNode.supernode?.insertSubnode(oneLineDustNode, aboveSubnode: self.oneLineNode.textNode) } if let oneLineDustNode = self.oneLineDustNode { - let textFrame = self.oneLineNode.frame.insetBy(dx: 0.0, dy: -3.0) + let textFrame = self.oneLineNode.textNode.frame.insetBy(dx: 0.0, dy: -3.0) oneLineDustNode.update(size: textFrame.size, color: .white, textColor: .white, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 0.0, dy: 3.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 0.0, dy: 3.0) }) oneLineDustNode.frame = textFrame diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index e8bfdf2ad8..685c651b28 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -11,6 +11,7 @@ import AccountContext import TelegramStringFormatting import UIKitRuntimeUtils import MediaResources +import AttachmentTextInputPanelNode public enum AttachmentButtonType: Equatable { case gallery @@ -167,6 +168,7 @@ public class AttachmentController: ViewController { private let buttons: [AttachmentButtonType] private let initialButton: AttachmentButtonType private let fromMenu: Bool + private let makeEntityInputView: () -> AttachmentTextInputPanelInputView? public var willDismiss: () -> Void = {} public var didDismiss: () -> Void = {} @@ -190,6 +192,7 @@ public class AttachmentController: ViewController { private let dim: ASDisplayNode private let shadowNode: ASImageNode private let container: AttachmentContainer + private let makeEntityInputView: () -> AttachmentTextInputPanelInputView? let panel: AttachmentPanel private var currentType: AttachmentButtonType? @@ -259,8 +262,9 @@ public class AttachmentController: ViewController { private let wrapperNode: ASDisplayNode - init(controller: AttachmentController) { + init(controller: AttachmentController, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { self.controller = controller + self.makeEntityInputView = makeEntityInputView self.dim = ASDisplayNode() self.dim.alpha = 0.0 @@ -274,7 +278,7 @@ public class AttachmentController: ViewController { self.container = AttachmentContainer() self.container.canHaveKeyboardFocus = true - self.panel = AttachmentPanel(context: controller.context, chatLocation: controller.chatLocation, updatedPresentationData: controller.updatedPresentationData) + self.panel = AttachmentPanel(context: controller.context, chatLocation: controller.chatLocation, updatedPresentationData: controller.updatedPresentationData, makeEntityInputView: makeEntityInputView) self.panel.fromMenu = controller.fromMenu self.panel.isStandalone = controller.isStandalone @@ -843,13 +847,14 @@ public class AttachmentController: ViewController { public var getInputContainerNode: () -> (CGFloat, ASDisplayNode, () -> AttachmentController.InputPanelTransition?)? = { return nil } - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, chatLocation: ChatLocation, buttons: [AttachmentButtonType], initialButton: AttachmentButtonType = .gallery, fromMenu: Bool = false) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, chatLocation: ChatLocation, buttons: [AttachmentButtonType], initialButton: AttachmentButtonType = .gallery, fromMenu: Bool = false, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { self.context = context self.updatedPresentationData = updatedPresentationData self.chatLocation = chatLocation self.buttons = buttons self.initialButton = initialButton self.fromMenu = fromMenu + self.makeEntityInputView = makeEntityInputView super.init(navigationBarPresentationData: nil) @@ -877,7 +882,7 @@ public class AttachmentController: ViewController { } open override func loadDisplayNode() { - self.displayNode = Node(controller: self) + self.displayNode = Node(controller: self, makeEntityInputView: self.makeEntityInputView) self.displayNodeDidLoad() } diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 547c91d193..996242b0a7 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -458,6 +458,8 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { private var presentationInterfaceState: ChatPresentationInterfaceState private var interfaceInteraction: ChatPanelInterfaceInteraction? + private let makeEntityInputView: () -> AttachmentTextInputPanelInputView? + private let containerNode: ASDisplayNode private let backgroundNode: NavigationBackgroundNode private let scrollNode: ASScrollNode @@ -496,9 +498,11 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { var mainButtonPressed: () -> Void = { } - init(context: AccountContext, chatLocation: ChatLocation, updatedPresentationData: (initial: PresentationData, signal: Signal)?) { + init(context: AccountContext, chatLocation: ChatLocation, updatedPresentationData: (initial: PresentationData, signal: Signal)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { self.context = context self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } + + self.makeEntityInputView = makeEntityInputView self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(previewing: false), chatLocation: chatLocation, subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil) @@ -895,7 +899,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { if let strongSelf = self { strongSelf.present(c) } - }) + }, makeEntityInputView: self.makeEntityInputView) textInputPanelNode.interfaceInteraction = self.interfaceInteraction textInputPanelNode.sendMessage = { [weak self] mode in if let strongSelf = self { diff --git a/submodules/Components/PagerComponent/Sources/PagerComponent.swift b/submodules/Components/PagerComponent/Sources/PagerComponent.swift index b14ec8d808..b3b0ce76ce 100644 --- a/submodules/Components/PagerComponent/Sources/PagerComponent.swift +++ b/submodules/Components/PagerComponent/Sources/PagerComponent.swift @@ -543,7 +543,11 @@ public final class PagerComponent Void public let switchToTextInput: () -> Void public let switchToGifSubject: (GifPagerContentComponent.Subject) -> Void - public let makeSearchContainerNode: (EntitySearchContentType) -> EntitySearchContainerNode + public let makeSearchContainerNode: (EntitySearchContentType) -> EntitySearchContainerNode? public let deviceMetrics: DeviceMetrics public let hiddenInputHeight: CGFloat public let isExpanded: Bool @@ -103,7 +103,7 @@ public final class EntityKeyboardComponent: Component { hideInputUpdated: @escaping (Bool, Bool, Transition) -> Void, switchToTextInput: @escaping () -> Void, switchToGifSubject: @escaping (GifPagerContentComponent.Subject) -> Void, - makeSearchContainerNode: @escaping (EntitySearchContentType) -> EntitySearchContainerNode, + makeSearchContainerNode: @escaping (EntitySearchContentType) -> EntitySearchContainerNode?, deviceMetrics: DeviceMetrics, hiddenInputHeight: CGFloat, isExpanded: Bool @@ -486,7 +486,8 @@ public final class EntityKeyboardComponent: Component { )), topPanel: AnyComponent(EntityKeyboardTopContainerPanelComponent( theme: component.theme, - overflowHeight: component.hiddenInputHeight + overflowHeight: component.hiddenInputHeight, + displayBackground: component.externalTopPanelContainer == nil )), externalTopPanelContainer: component.externalTopPanelContainer, bottomPanel: AnyComponent(EntityKeyboardBottomPanelComponent( diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift index 495748e37b..a1925f41d8 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift @@ -278,59 +278,64 @@ final class EntityKeyboardBottomPanelComponent: Component { let navigateToContentId = panelEnvironment.navigateToContentId - for icon in panelEnvironment.contentIcons { - validIconIds.append(icon.id) - - var iconTransition = transition - let iconView: ComponentHostView - if let current = self.iconViews[icon.id] { - iconView = current - } else { - iconTransition = .immediate - iconView = ComponentHostView() - self.iconViews[icon.id] = iconView - self.addSubview(iconView) + if panelEnvironment.contentIcons.count > 1 { + for icon in panelEnvironment.contentIcons { + validIconIds.append(icon.id) + + var iconTransition = transition + let iconView: ComponentHostView + if let current = self.iconViews[icon.id] { + iconView = current + } else { + iconTransition = .immediate + iconView = ComponentHostView() + self.iconViews[icon.id] = iconView + self.addSubview(iconView) + } + + let iconSize = iconView.update( + transition: iconTransition, + component: AnyComponent(BottomPanelIconComponent( + content: icon.component, + action: { + navigateToContentId(icon.id) + } + )), + environment: {}, + containerSize: CGSize(width: 32.0, height: 32.0) + ) + + iconInfos[icon.id] = (size: iconSize, transition: iconTransition) + + if !iconTotalSize.width.isZero { + iconTotalSize.width += iconSpacing + } + iconTotalSize.width += iconSize.width + iconTotalSize.height = max(iconTotalSize.height, iconSize.height) } - - let iconSize = iconView.update( - transition: iconTransition, - component: AnyComponent(BottomPanelIconComponent( - content: icon.component, - action: { - navigateToContentId(icon.id) - } - )), - environment: {}, - containerSize: CGSize(width: 32.0, height: 32.0) - ) - - iconInfos[icon.id] = (size: iconSize, transition: iconTransition) - - if !iconTotalSize.width.isZero { - iconTotalSize.width += iconSpacing - } - iconTotalSize.width += iconSize.width - iconTotalSize.height = max(iconTotalSize.height, iconSize.height) } var nextIconOrigin = CGPoint(x: floor((availableSize.width - iconTotalSize.width) / 2.0), y: floor((intrinsicHeight - iconTotalSize.height) / 2.0)) if component.bottomInset > 0.0 { nextIconOrigin.y += 2.0 } - for icon in panelEnvironment.contentIcons { - guard let iconInfo = iconInfos[icon.id], let iconView = self.iconViews[icon.id] else { - continue + + if panelEnvironment.contentIcons.count > 1 { + for icon in panelEnvironment.contentIcons { + guard let iconInfo = iconInfos[icon.id], let iconView = self.iconViews[icon.id] else { + continue + } + + let iconFrame = CGRect(origin: nextIconOrigin, size: iconInfo.size) + iconInfo.transition.setFrame(view: iconView, frame: iconFrame, completion: nil) + + if let activeContentId = activeContentId, activeContentId == icon.id { + self.highlightedIconBackgroundView.isHidden = false + transition.setFrame(view: self.highlightedIconBackgroundView, frame: iconFrame) + } + + nextIconOrigin.x += iconInfo.size.width + iconSpacing } - - let iconFrame = CGRect(origin: nextIconOrigin, size: iconInfo.size) - iconInfo.transition.setFrame(view: iconView, frame: iconFrame, completion: nil) - - if let activeContentId = activeContentId, activeContentId == icon.id { - self.highlightedIconBackgroundView.isHidden = false - transition.setFrame(view: self.highlightedIconBackgroundView, frame: iconFrame) - } - - nextIconOrigin.x += iconInfo.size.width + iconSpacing } if activeContentId == nil { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopContainerPanelComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopContainerPanelComponent.swift index ba093de32b..54bacc37aa 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopContainerPanelComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopContainerPanelComponent.swift @@ -32,13 +32,16 @@ final class EntityKeyboardTopContainerPanelComponent: Component { let theme: PresentationTheme let overflowHeight: CGFloat + let displayBackground: Bool init( theme: PresentationTheme, - overflowHeight: CGFloat + overflowHeight: CGFloat, + displayBackground: Bool ) { self.theme = theme self.overflowHeight = overflowHeight + self.displayBackground = displayBackground } static func ==(lhs: EntityKeyboardTopContainerPanelComponent, rhs: EntityKeyboardTopContainerPanelComponent) -> Bool { @@ -48,6 +51,9 @@ final class EntityKeyboardTopContainerPanelComponent: Component { if lhs.overflowHeight != rhs.overflowHeight { return false } + if lhs.displayBackground != rhs.displayBackground { + return false + } return true } @@ -59,6 +65,9 @@ final class EntityKeyboardTopContainerPanelComponent: Component { } final class View: UIView { + private var backgroundView: BlurredBackgroundView? + private var backgroundSeparatorView: UIView? + private var panelViews: [AnyHashable: PanelView] = [:] private var component: EntityKeyboardTopContainerPanelComponent? @@ -183,6 +192,40 @@ final class EntityKeyboardTopContainerPanelComponent: Component { strongSelf.updateVisibilityFraction(value: fraction, transition: transition) } + if component.displayBackground { + let backgroundView: BlurredBackgroundView + if let current = self.backgroundView { + backgroundView = current + } else { + backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true) + self.insertSubview(backgroundView, at: 0) + } + + let backgroundSeparatorView: UIView + if let current = self.backgroundSeparatorView { + backgroundSeparatorView = current + } else { + backgroundSeparatorView = UIView() + self.insertSubview(backgroundSeparatorView, aboveSubview: backgroundView) + } + + backgroundView.updateColor(color: component.theme.chat.inputPanel.panelBackgroundColor.withMultipliedAlpha(1.0), transition: .immediate) + backgroundView.update(size: CGSize(width: availableSize.width, height: height), transition: transition.containedViewLayoutTransition) + transition.setFrame(view: backgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height))) + + backgroundSeparatorView.backgroundColor = component.theme.chat.inputPanel.panelSeparatorColor + transition.setFrame(view: backgroundSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: height), size: CGSize(width: availableSize.width, height: UIScreenPixel))) + } else { + if let backgroundView = self.backgroundView { + self.backgroundView = nil + backgroundView.removeFromSuperview() + } + if let backgroundSeparatorView = self.backgroundSeparatorView { + self.backgroundSeparatorView = nil + backgroundSeparatorView.removeFromSuperview() + } + } + return CGSize(width: availableSize.width, height: height) } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntitySearchContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntitySearchContentComponent.swift index a6febf912f..6424d92c18 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntitySearchContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntitySearchContentComponent.swift @@ -51,11 +51,11 @@ final class EntitySearchContentEnvironment: Equatable { final class EntitySearchContentComponent: Component { typealias EnvironmentType = EntitySearchContentEnvironment - let makeContainerNode: () -> EntitySearchContainerNode + let makeContainerNode: () -> EntitySearchContainerNode? let dismissSearch: () -> Void init( - makeContainerNode: @escaping () -> EntitySearchContainerNode, + makeContainerNode: @escaping () -> EntitySearchContainerNode?, dismissSearch: @escaping () -> Void ) { self.makeContainerNode = makeContainerNode @@ -78,30 +78,34 @@ final class EntitySearchContentComponent: Component { } func update(component: EntitySearchContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { - let containerNode: EntitySearchContainerNode + let containerNode: EntitySearchContainerNode? if let current = self.containerNode { containerNode = current } else { containerNode = component.makeContainerNode() - self.containerNode = containerNode - self.addSubnode(containerNode) + if let containerNode = containerNode { + self.containerNode = containerNode + self.addSubnode(containerNode) + } } + if let containerNode = containerNode { + let environmentValue = environment[EntitySearchContentEnvironment.self].value - - transition.setFrame(view: containerNode.view, frame: CGRect(origin: CGPoint(), size: availableSize)) - containerNode.updateLayout( - size: availableSize, - leftInset: 0.0, - rightInset: 0.0, - bottomInset: 0.0, - inputHeight: 0.0, - deviceMetrics: environmentValue.deviceMetrics, - transition: transition.containedViewLayoutTransition - ) - - containerNode.onCancel = { - component.dismissSearch() + transition.setFrame(view: containerNode.view, frame: CGRect(origin: CGPoint(), size: availableSize)) + containerNode.updateLayout( + size: availableSize, + leftInset: 0.0, + rightInset: 0.0, + bottomInset: 0.0, + inputHeight: 0.0, + deviceMetrics: environmentValue.deviceMetrics, + transition: transition.containedViewLayoutTransition + ) + + containerNode.onCancel = { + component.dismissSearch() + } } return availableSize diff --git a/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift b/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift index e8fdca3051..1b58dcbb53 100644 --- a/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift +++ b/submodules/TelegramUI/Components/TextNodeWithEntities/Sources/TextNodeWithEntities.swift @@ -118,7 +118,7 @@ public final class TextNodeWithEntities { if let font = string.attribute(.font, at: range.location, effectiveRange: nil) as? UIFont { string.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize), range: range) - let itemSize = font.pointSize * 24.0 / 17.0 / CGFloat(range.length) + let itemSize = font.pointSize * 24.0 / 17.0 / CGFloat(min(2, range.length)) let runDelegateData = RunDelegateData( ascent: font.ascender, diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 26fab2afb2..917b839354 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -10732,7 +10732,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) - let inputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: presentationInterfaceState, isCaption: true, presentController: { _ in }) + let inputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: presentationInterfaceState, isCaption: true, presentController: { _ in }, makeEntityInputView: { [weak self] in + guard let strongSelf = self else { + return nil + } + + return EntityInputView(context: strongSelf.context, isDark: true) + }) inputPanelNode.interfaceInteraction = interfaceInteraction inputPanelNode.effectivePresentationInterfaceState = { return presentationInterfaceState @@ -10967,7 +10973,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let currentFilesController = Atomic(value: nil) let currentLocationController = Atomic(value: nil) - let attachmentController = AttachmentController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, chatLocation: strongSelf.chatLocation, buttons: buttons, initialButton: initialButton) + let attachmentController = AttachmentController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, chatLocation: strongSelf.chatLocation, buttons: buttons, initialButton: initialButton, makeEntityInputView: { [weak self] in + guard let strongSelf = self else { + return nil + } + return EntityInputView(context: strongSelf.context, isDark: false) + }) attachmentController.requestController = { [weak self, weak attachmentController] type, completion in guard let strongSelf = self else { return diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index b578b8dcf9..0e1186d180 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -540,14 +540,13 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } self.addSubnode(self.inputPanelContainerNode) + self.addSubnode(self.inputContextPanelContainer) self.inputPanelContainerNode.addSubnode(self.inputPanelClippingNode) self.inputPanelClippingNode.addSubnode(self.inputPanelBackgroundNode) self.inputPanelClippingNode.addSubnode(self.inputPanelBackgroundSeparatorNode) self.inputPanelBackgroundNode.addSubnode(self.inputPanelBottomBackgroundSeparatorNode) - self.contentContainerNode.addSubnode(self.inputContextPanelContainer) - self.addSubnode(self.messageTransitionNode) self.contentContainerNode.addSubnode(self.navigateButtons) self.contentContainerNode.addSubnode(self.presentationContextMarker) diff --git a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift index 0def32c9a9..22b3258fb6 100644 --- a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift @@ -21,6 +21,8 @@ import AudioToolbox import UndoUI import ContextUI import GalleryUI +import AttachmentTextInputPanelNode +import TelegramPresentationData private let staticEmojiMapping: [(EmojiPagerContentComponent.StaticEmojiSegment, [String])] = { guard let path = getAppBundle().path(forResource: "emoji1016", ofType: "txt") else { @@ -46,14 +48,14 @@ private let staticEmojiMapping: [(EmojiPagerContentComponent.StaticEmojiSegment, final class ChatEntityKeyboardInputNode: ChatInputNode { struct InputData: Equatable { var emoji: EmojiPagerContentComponent - var stickers: EmojiPagerContentComponent - var gifs: GifPagerContentComponent + var stickers: EmojiPagerContentComponent? + var gifs: GifPagerContentComponent? var availableGifSearchEmojies: [EntityKeyboardComponent.GifSearchEmoji] init( emoji: EmojiPagerContentComponent, - stickers: EmojiPagerContentComponent, - gifs: GifPagerContentComponent, + stickers: EmojiPagerContentComponent?, + gifs: GifPagerContentComponent?, availableGifSearchEmojies: [EntityKeyboardComponent.GifSearchEmoji] ) { self.emoji = emoji @@ -63,7 +65,109 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { } } - static func inputData(context: AccountContext, interfaceInteraction: ChatPanelInterfaceInteraction, controllerInteraction: ChatControllerInteraction, chatPeerId: PeerId?) -> Signal { + static func emojiInputData(context: AccountContext, inputInteraction: EmojiPagerContentComponent.InputInteraction, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) -> Signal { + let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> map { peer -> Bool in + guard case let .user(user) = peer else { + return false + } + return user.isPremium + } + |> distinctUntilChanged + + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + let isPremiumDisabled = premiumConfiguration.isPremiumDisabled + + let emojiItems: Signal = combineLatest( + context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000), + hasPremium + ) + |> map { view, hasPremium -> EmojiPagerContentComponent in + struct ItemGroup { + var supergroupId: AnyHashable + var id: AnyHashable + var isPremium: Bool + var items: [EmojiPagerContentComponent.Item] + } + var itemGroups: [ItemGroup] = [] + var itemGroupIndexById: [AnyHashable: Int] = [:] + + for (subgroupId, list) in staticEmojiMapping { + let groupId: AnyHashable = "static" + for emojiString in list { + let resultItem = EmojiPagerContentComponent.Item( + file: nil, + staticEmoji: emojiString, + subgroupId: subgroupId.rawValue + ) + + if let groupIndex = itemGroupIndexById[groupId] { + itemGroups[groupIndex].items.append(resultItem) + } else { + itemGroupIndexById[groupId] = itemGroups.count + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, isPremium: false, items: [resultItem])) + } + } + } + + for entry in view.entries { + guard let item = entry.item as? StickerPackItem else { + continue + } + let resultItem = EmojiPagerContentComponent.Item( + file: item.file, + staticEmoji: nil, + subgroupId: nil + ) + + let supergroupId = entry.index.collectionId + let groupId: AnyHashable = supergroupId + let isPremium: Bool = item.file.isPremiumEmoji && !hasPremium + if isPremium && isPremiumDisabled { + continue + } + /*if isPremium { + groupId = "\(supergroupId)-p" + } else { + groupId = supergroupId + }*/ + if let groupIndex = itemGroupIndexById[groupId] { + itemGroups[groupIndex].items.append(resultItem) + } else { + itemGroupIndexById[groupId] = itemGroups.count + itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, isPremium: isPremium, items: [resultItem])) + } + } + + return EmojiPagerContentComponent( + id: "emoji", + context: context, + animationCache: animationCache, + animationRenderer: animationRenderer, + inputInteraction: inputInteraction, + itemGroups: itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in + var title: String? + if group.id == AnyHashable("recent") { + //TODO:localize + title = "Recently Used" + } else { + for (id, info, _) in view.collectionInfos { + if AnyHashable(id) == group.id, let info = info as? StickerPackCollectionInfo { + title = info.title + break + } + } + } + + return EmojiPagerContentComponent.ItemGroup(supergroupId: group.supergroupId, groupId: group.id, title: title, isPremium: group.isPremium, displayPremiumBadges: false, items: group.items) + }, + itemLayoutType: .compact + ) + } + return emojiItems + } + + static func inputData(context: AccountContext, interfaceInteraction: ChatPanelInterfaceInteraction, controllerInteraction: ChatControllerInteraction?, chatPeerId: PeerId?) -> Signal { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) let isPremiumDisabled = premiumConfiguration.isPremiumDisabled @@ -177,9 +281,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { chatPeerId: chatPeerId ) let stickerInputInteraction = EmojiPagerContentComponent.InputInteraction( - performItemAction: { [weak interfaceInteraction] item, view, rect, layer in + performItemAction: { [weak controllerInteraction, weak interfaceInteraction] item, view, rect, layer in let _ = (hasPremium |> take(1) |> deliverOnMainQueue).start(next: { hasPremium in - guard let interfaceInteraction = interfaceInteraction else { + guard let controllerInteraction = controllerInteraction, let interfaceInteraction = interfaceInteraction else { return } if let file = item.file { @@ -249,98 +353,13 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { animationRenderer = MultiAnimationRendererImpl() //} - let orderedItemListCollectionIds: [Int32] = [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.PremiumStickers, Namespaces.OrderedItemList.CloudPremiumStickers] - let namespaces: [ItemCollectionId.Namespace] = [Namespaces.ItemCollection.CloudStickerPacks] + let emojiItems = emojiInputData(context: context, inputInteraction: emojiInputInteraction, animationCache: animationCache, animationRenderer: animationRenderer) - let emojiItems: Signal = combineLatest( - context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000), - hasPremium - ) - |> map { view, hasPremium -> EmojiPagerContentComponent in - struct ItemGroup { - var supergroupId: AnyHashable - var id: AnyHashable - var isPremium: Bool - var items: [EmojiPagerContentComponent.Item] - } - var itemGroups: [ItemGroup] = [] - var itemGroupIndexById: [AnyHashable: Int] = [:] - - for (subgroupId, list) in staticEmojiMapping { - let groupId: AnyHashable = "static" - for emojiString in list { - let resultItem = EmojiPagerContentComponent.Item( - file: nil, - staticEmoji: emojiString, - subgroupId: subgroupId.rawValue - ) - - if let groupIndex = itemGroupIndexById[groupId] { - itemGroups[groupIndex].items.append(resultItem) - } else { - itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, isPremium: false, items: [resultItem])) - } - } - } - - for entry in view.entries { - guard let item = entry.item as? StickerPackItem else { - continue - } - let resultItem = EmojiPagerContentComponent.Item( - file: item.file, - staticEmoji: nil, - subgroupId: nil - ) - - let supergroupId = entry.index.collectionId - let groupId: AnyHashable = supergroupId - let isPremium: Bool = item.file.isPremiumEmoji && !hasPremium - if isPremium && isPremiumDisabled { - continue - } - /*if isPremium { - groupId = "\(supergroupId)-p" - } else { - groupId = supergroupId - }*/ - if let groupIndex = itemGroupIndexById[groupId] { - itemGroups[groupIndex].items.append(resultItem) - } else { - itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, isPremium: isPremium, items: [resultItem])) - } - } - - return EmojiPagerContentComponent( - id: "emoji", - context: context, - animationCache: animationCache, - animationRenderer: animationRenderer, - inputInteraction: emojiInputInteraction, - itemGroups: itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in - var title: String? - if group.id == AnyHashable("recent") { - //TODO:localize - title = "Recently Used" - } else { - for (id, info, _) in view.collectionInfos { - if AnyHashable(id) == group.id, let info = info as? StickerPackCollectionInfo { - title = info.title - break - } - } - } - - return EmojiPagerContentComponent.ItemGroup(supergroupId: group.supergroupId, groupId: group.id, title: title, isPremium: group.isPremium, displayPremiumBadges: false, items: group.items) - }, - itemLayoutType: .compact - ) - } + let stickerNamespaces: [ItemCollectionId.Namespace] = [Namespaces.ItemCollection.CloudStickerPacks] + let stickerOrderedItemListCollectionIds: [Int32] = [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.PremiumStickers, Namespaces.OrderedItemList.CloudPremiumStickers] let stickerItems: Signal = combineLatest( - context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: orderedItemListCollectionIds, namespaces: namespaces, aroundIndex: nil, count: 10000000), + context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: stickerOrderedItemListCollectionIds, namespaces: stickerNamespaces, aroundIndex: nil, count: 10000000), hasPremium ) |> map { view, hasPremium -> EmojiPagerContentComponent in @@ -604,7 +623,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { private var currentInputData: InputData private var inputDataDisposable: Disposable? - private let controllerInteraction: ChatControllerInteraction + private let controllerInteraction: ChatControllerInteraction? private var inputNodeInteraction: ChatMediaInputNodeInteraction? @@ -617,6 +636,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { return self.externalTopPanelContainerImpl } + var switchToTextInput: (() -> Void)? + private var currentState: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool, isExpanded: Bool)? private var gifMode: GifPagerContentComponent.Subject = .recent { @@ -811,7 +832,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { private let gifComponent = Promise() private var gifInputInteraction: GifPagerContentComponent.InputInteraction? - init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal, defaultToEmojiTab: Bool, controllerInteraction: ChatControllerInteraction) { + init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal, defaultToEmojiTab: Bool, controllerInteraction: ChatControllerInteraction?) { self.context = context self.currentInputData = currentInputData self.defaultToEmojiTab = defaultToEmojiTab @@ -898,6 +919,15 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { } ) + self.switchToTextInput = { [weak self] in + guard let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction else { + return + } + controllerInteraction.updateInputMode { _ in + return .text + } + } + self.reloadGifContext() } @@ -996,12 +1026,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { } }, switchToTextInput: { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.controllerInteraction.updateInputMode { _ in - return .text - } + self?.switchToTextInput?() }, switchToGifSubject: { [weak self] subject in guard let strongSelf = self else { @@ -1009,7 +1034,11 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { } strongSelf.gifMode = subject }, - makeSearchContainerNode: { content in + makeSearchContainerNode: { [weak controllerInteraction] content in + guard let controllerInteraction = controllerInteraction else { + return nil + } + let mappedMode: ChatMediaInputSearchMode switch content { case .stickers: @@ -1074,7 +1103,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { }, action: { _, f in f(.default) if isSaved { - let _ = self?.controllerInteraction.sendGif(FileMediaReference.savedGif(media: file), sourceView, sourceRect, false, false) + let _ = self?.controllerInteraction?.sendGif(FileMediaReference.savedGif(media: file), sourceView, sourceRect, false, false) }/* else if let (collection, result) = file.contextResult { let _ = self?.controllerInteraction.sendBotContextResultAsGif(collection, result, sourceNode, sourceRect, false) }*/ @@ -1141,7 +1170,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { |> deliverOnMainQueue).start(next: { result in switch result { case .generic: - controllerInteraction.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) + controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) case let .limitExceeded(limit, premiumLimit): let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) let text: String @@ -1150,10 +1179,10 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { } else { text = presentationData.strings.Premium_MaxSavedGifsText("\(premiumLimit)").string } - controllerInteraction.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text), elevatedLayout: false, animateInAsReplacement: false, action: { action in + controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text), elevatedLayout: false, animateInAsReplacement: false, action: { action in if case .info = action { let controller = PremiumIntroScreen(context: context, source: .savedGifs) - controllerInteraction.navigationController()?.pushViewController(controller) + controllerInteraction?.navigationController()?.pushViewController(controller) return true } return false @@ -1164,7 +1193,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { } let contextController = ContextController(account: strongSelf.context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceView: sourceView, sourceRect: sourceRect)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) - strongSelf.controllerInteraction.presentGlobalOverlayController(contextController, nil) + strongSelf.controllerInteraction?.presentGlobalOverlayController(contextController, nil) }) } } @@ -1202,3 +1231,220 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent } } } + +final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputViewAudioFeedback { + private let context: AccountContext + + public var insertText: ((NSAttributedString) -> Void)? + public var deleteBackwards: (() -> Void)? + public var switchToKeyboard: (() -> Void)? + public var presentController: ((ViewController) -> Void)? + + private var presentationData: PresentationData + private var inputNode: ChatEntityKeyboardInputNode? + private let animationCache: AnimationCache + private let animationRenderer: MultiAnimationRenderer + + init( + context: AccountContext, + isDark: Bool + ) { + self.context = context + + self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: { + return TempBox.shared.tempFile(fileName: "file").path + }) + self.animationRenderer = MultiAnimationRendererImpl() + + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + if isDark { + self.presentationData = self.presentationData.withUpdated(theme: defaultDarkPresentationTheme) + } + + //super.init(frame: CGRect(origin: CGPoint(), size: CGSize(width: 1.0, height: 1.0)), inputViewStyle: .default) + super.init(frame: CGRect(origin: CGPoint(), size: CGSize(width: 1.0, height: 1.0))) + + self.autoresizingMask = [.flexibleWidth, .flexibleHeight] + self.clipsToBounds = true + + let inputInteraction = EmojiPagerContentComponent.InputInteraction( + performItemAction: { [weak self] item, _, _, _ in + guard let strongSelf = self else { + return + } + let hasPremium = strongSelf.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId)) + |> map { peer -> Bool in + guard case let .user(user) = peer else { + return false + } + return user.isPremium + } + |> distinctUntilChanged + + let _ = (hasPremium |> take(1) |> deliverOnMainQueue).start(next: { hasPremium in + guard let strongSelf = self else { + return + } + + if let file = item.file { + var text = "." + var emojiAttribute: ChatTextInputTextCustomEmojiAttribute? + loop: for attribute in file.attributes { + switch attribute { + case let .CustomEmoji(_, displayText, packReference): + text = displayText + emojiAttribute = ChatTextInputTextCustomEmojiAttribute(stickerPack: packReference, fileId: file.fileId.id, file: file) + break loop + default: + break + } + } + + if file.isPremiumEmoji && !hasPremium { + //TODO:localize + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + strongSelf.presentController?(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: nil, text: "Subscribe to Telegram Premium to unlock this emoji.", undoText: "More", customAction: { + guard let strongSelf = self else { + return + } + + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumDemoScreen(context: strongSelf.context, subject: .premiumStickers, action: { + let controller = PremiumIntroScreen(context: strongSelf.context, source: .stickers) + replaceImpl?(controller) + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + strongSelf.presentController?(controller) + }), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false })) + return + } + + if let emojiAttribute = emojiAttribute { + AudioServicesPlaySystemSound(0x450) + strongSelf.insertText?(NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute])) + } + } else if let staticEmoji = item.staticEmoji { + AudioServicesPlaySystemSound(0x450) + strongSelf.insertText?(NSAttributedString(string: staticEmoji, attributes: [:])) + } + }) + }, + deleteBackwards: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.deleteBackwards?() + }, + openStickerSettings: { + }, + openPremiumSection: { + }, + pushController: { _ in + }, + presentController: { _ in + }, + presentGlobalOverlayController: { _ in + }, + navigationController: { + return nil + }, + sendSticker: nil, + chatPeerId: nil + ) + + let semaphore = DispatchSemaphore(value: 0) + var emojiComponent: EmojiPagerContentComponent? + let _ = ChatEntityKeyboardInputNode.emojiInputData(context: context, inputInteraction: inputInteraction, animationCache: self.animationCache, animationRenderer: self.animationRenderer).start(next: { value in + emojiComponent = value + semaphore.signal() + }) + semaphore.wait() + + if let emojiComponent = emojiComponent { + let inputNode = ChatEntityKeyboardInputNode( + context: self.context, + currentInputData: ChatEntityKeyboardInputNode.InputData( + emoji: emojiComponent, + stickers: nil, + gifs: nil, + availableGifSearchEmojies: [] + ), + updatedInputData: .never(), + defaultToEmojiTab: true, + controllerInteraction: nil + ) + self.inputNode = inputNode + inputNode.externalTopPanelContainerImpl = nil + inputNode.switchToTextInput = { [weak self] in + self?.switchToKeyboard?() + } + self.addSubnode(inputNode) + } + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + + guard let inputNode = self.inputNode else { + return + } + + for view in self.subviews { + if view !== inputNode.view { + view.isHidden = true + } + } + + let bottomInset: CGFloat + if #available(iOS 11.0, *) { + bottomInset = max(0.0, UIScreen.main.bounds.height - (self.window?.safeAreaLayoutGuide.layoutFrame.maxY ?? 10000.0)) + } else { + bottomInset = 0.0 + } + + let presentationInterfaceState = ChatPresentationInterfaceState( + chatWallpaper: .builtin(WallpaperSettings()), + theme: self.presentationData.theme, + strings: self.presentationData.strings, + dateTimeFormat: self.presentationData.dateTimeFormat, + nameDisplayOrder: self.presentationData.nameDisplayOrder, + limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, + fontSize: self.presentationData.chatFontSize, + bubbleCorners: self.presentationData.chatBubbleCorners, + accountPeerId: self.context.account.peerId, + mode: .standard(previewing: false), + chatLocation: .peer(id: self.context.account.peerId), + subject: nil, + peerNearbyData: nil, + greetingData: nil, + pendingUnpinnedAllMessages: false, + activeGroupCallInfo: nil, + hasActiveGroupCall: false, + importState: nil + ) + + let _ = inputNode.updateLayout( + width: self.bounds.width, + leftInset: 0.0, + rightInset: 0.0, + bottomInset: bottomInset, + standardInputHeight: self.bounds.height, + inputHeight: self.bounds.height, + maximumHeight: self.bounds.height, + inputPanelHeight: 0.0, + transition: .immediate, + interfaceState: presentationInterfaceState, + deviceMetrics: DeviceMetrics.iPhone12, + isVisible: true, + isExpanded: false + ) + inputNode.frame = self.bounds + } +} diff --git a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift index a99acd9b86..6693d99a67 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift @@ -390,7 +390,9 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.addSubnode(forwardAccessoryPanelNode) self.forwardAccessoryPanelNode = forwardAccessoryPanelNode - let textInputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: self.presentationInterfaceState, presentController: { [weak self] c in self?.present(c, nil) }) + let textInputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: self.presentationInterfaceState, presentController: { [weak self] c in self?.present(c, nil) }, makeEntityInputView: { + return nil + }) textInputPanelNode.interfaceInteraction = self.interfaceInteraction textInputPanelNode.sendMessage = { [weak self] mode in guard let strongSelf = self else { diff --git a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift index a80f41176a..4469e3bf1a 100644 --- a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift @@ -70,6 +70,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode { self.textNode.maximumNumberOfLines = 1 self.textNode.displaysAsynchronously = false self.textNode.insets = UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0) + self.textNode.visibility = true if let animationCache = animationCache, let animationRenderer = animationRenderer { self.textNode.arguments = TextNodeWithEntities.Arguments( diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index f51919647f..716b0ef356 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -1212,7 +1212,9 @@ private final class WebAppContextReferenceContentSource: ContextReferenceContent } public func standaloneWebAppController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, params: WebAppParameters, openUrl: @escaping (String) -> Void, getInputContainerNode: @escaping () -> (CGFloat, ASDisplayNode, () -> AttachmentController.InputPanelTransition?)? = { return nil }, completion: @escaping () -> Void = {}, willDismiss: @escaping () -> Void = {}, didDismiss: @escaping () -> Void = {}, getNavigationController: @escaping () -> NavigationController? = { return nil }) -> ViewController { - let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: .peer(id: params.peerId), buttons: [.standalone], initialButton: .standalone, fromMenu: params.fromMenu) + let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: .peer(id: params.peerId), buttons: [.standalone], initialButton: .standalone, fromMenu: params.fromMenu, makeEntityInputView: { + return nil + }) controller.getInputContainerNode = getInputContainerNode controller.requestController = { _, present in let webAppController = WebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, replyToMessageId: nil)