[WIP] Polls

This commit is contained in:
Ilya Laktyushin 2026-03-20 14:52:19 +01:00
parent 676f46d44d
commit f12fb93a3e
10 changed files with 184 additions and 66 deletions

View file

@ -260,7 +260,7 @@ public extension TelegramEngine {
return _internal_addPollOption(account: self.account, messageId: messageId, text: text, entities: entities, mediaReference: mediaReference)
}
public func deletePollOption(messageId: MessageId, opaqueIdentifier: Data, mediaReference: AnyMediaReference? = nil) -> Signal<Never, DeletePollOptionError> {
public func deletePollOption(messageId: MessageId, opaqueIdentifier: Data) -> Signal<Never, DeletePollOptionError> {
return _internal_deletePollOption(account: self.account, messageId: messageId, opaqueIdentifier: opaqueIdentifier)
}

View file

@ -20,6 +20,42 @@ private func customEmojiAttributes(primaryTextColor: UIColor, emoji: ChatTextInp
return MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: emoji])
}
private func serviceMessageArgumentRange(index: Int, value: String, in stringWithRanges: (String, [(Int, NSRange)])) -> NSRange? {
if let range = stringWithRanges.1.first(where: { $0.0 == index })?.1 {
return range
}
let string = stringWithRanges.0 as NSString
return stringWithRanges.1.map { $0.1 }.first(where: { range in
NSMaxRange(range) <= string.length && string.substring(with: range) == value
})
}
private func addServiceMessageTextEntities(_ entities: [MessageTextEntity], to attributedString: NSMutableAttributedString, text: String, range: NSRange, associatedMedia: [MediaId: Media]) {
let textLength = min((text as NSString).length, range.length)
for entity in entities {
if entity.range.lowerBound >= textLength {
continue
}
let length = min(entity.range.count, textLength - entity.range.lowerBound)
if length <= 0 {
continue
}
let entityRange = NSRange(location: range.location + entity.range.lowerBound, length: length)
switch entity.type {
case .Spoiler:
attributedString.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler), value: true, range: entityRange)
case let .CustomEmoji(_, fileId):
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile), range: entityRange)
default:
break
}
}
}
private func peerMentionAttributes(primaryTextColor: UIColor, peerId: EnginePeer.Id) -> MarkdownAttributeSet {
return MarkdownAttributeSet(font: titleBoldFont, textColor: primaryTextColor, additionalAttributes: [TelegramTextAttributes.PeerMention: TelegramPeerMention(peerId: peerId, mention: "")])
}
@ -1796,13 +1832,26 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
if optionTitle.count > 20 {
optionTitle = optionTitle.prefix(20) + ""
}
let optionEntities = option.entities.filter { entity in
switch entity.type {
case .Spoiler, .CustomEmoji:
return true
default:
return false
}
}
if message.author?.id == accountPeerId {
var optionText = option.text
if optionText.count > 20 {
optionText = optionText.prefix(20) + ""
}
let resultString = strings.Notification_PollAddedOptionYou(optionText)
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes, 1: boldAttributes])
let stringWithRanges = resultString._tuple
let resultAttributedString = NSMutableAttributedString(attributedString: addAttributesToStringWithRanges(stringWithRanges, body: bodyAttributes, argumentAttributes: [0: boldAttributes, 1: boldAttributes]))
if let optionRange = serviceMessageArgumentRange(index: 0, value: optionText, in: stringWithRanges) {
addServiceMessageTextEntities(optionEntities, to: resultAttributedString, text: optionText, range: optionRange, associatedMedia: message.associatedMedia)
}
attributedString = resultAttributedString
} else {
let peerName = message.author?.compactDisplayTitle ?? ""
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])
@ -1813,7 +1862,12 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
optionText = optionText.prefix(20) + ""
}
let resultString = strings.Notification_PollAddedOption(peerName, optionText)
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: attributes)
let stringWithRanges = resultString._tuple
let resultAttributedString = NSMutableAttributedString(attributedString: addAttributesToStringWithRanges(stringWithRanges, body: bodyAttributes, argumentAttributes: attributes))
if let optionRange = serviceMessageArgumentRange(index: 1, value: optionText, in: stringWithRanges) {
addServiceMessageTextEntities(optionEntities, to: resultAttributedString, text: optionText, range: optionRange, associatedMedia: message.associatedMedia)
}
attributedString = resultAttributedString
}
case .unknown:
attributedString = nil

View file

@ -728,7 +728,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
return
}
guard self.forceSelected == nil else {
guard self.forceSelected == nil && self.currentResult == nil else {
return
}
@ -1359,6 +1359,8 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
private var currentStrings: PresentationStrings?
private var currentTheme: PresentationTheme?
private var currentIncoming = false
private var currentFocusedTextInputIsMedia = false
private var currentModeSelectorAnimationName: String?
var textUpdated: ((NSAttributedString) -> Void)?
var heightUpdated: (() -> Void)?
@ -1444,7 +1446,7 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
textColor: currentTextColor,
accentColor: currentTintColor,
insets: UIEdgeInsets(top: ChatMessagePollAddOptionNode.verticalInset, left: 0.0, bottom: ChatMessagePollAddOptionNode.verticalInset, right: 0.0),
hideKeyboard: false,
hideKeyboard: self.currentFocusedTextInputIsMedia,
customInputView: nil,
placeholder: NSAttributedString(string: strings.CreatePoll_AddOption, font: font, textColor: currentPlaceholderColor),
resetText: nil,
@ -1530,8 +1532,8 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
return max(ChatMessagePollAddOptionNode.minHeight, measureSize.height + ChatMessagePollAddOptionNode.verticalInset * 2.0)
}
static func asyncLayout(_ maybeNode: ChatMessagePollAddOptionNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ strings: PresentationStrings, _ incoming: Bool, _ text: NSAttributedString, _ attachment: Attachment?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool) -> ChatMessagePollAddOptionNode))) {
return { context, presentationData, strings, incoming, text, attachment, constrainedWidth in
static func asyncLayout(_ maybeNode: ChatMessagePollAddOptionNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ strings: PresentationStrings, _ incoming: Bool, _ focusedTextInputIsMedia: Bool, _ text: NSAttributedString, _ attachment: Attachment?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool) -> ChatMessagePollAddOptionNode))) {
return { context, presentationData, strings, incoming, focusedTextInputIsMedia, text, attachment, constrainedWidth in
let font = presentationData.messageFont
let textColor = incoming ? presentationData.theme.theme.chat.message.incoming.primaryTextColor : presentationData.theme.theme.chat.message.outgoing.primaryTextColor
let secondaryTextColor = incoming ? presentationData.theme.theme.chat.message.incoming.secondaryTextColor : presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
@ -1565,6 +1567,7 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
node.currentContext = context
node.currentTheme = presentationData.theme.theme
node.currentIncoming = incoming
node.currentFocusedTextInputIsMedia = focusedTextInputIsMedia
let textFieldSize = node.updateTextFieldLayout(size: size, forceUpdate: false)
node.currentMeasuredHeight = max(ChatMessagePollAddOptionNode.minHeight, textFieldSize.height)
node.currentTextValue = node._textFieldExternalState.text
@ -1607,8 +1610,19 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
self.addIconNode.isHidden = displaySelector
if displaySelector {
var playAnimation = false
let modeSelectorSize = CGSize(width: 32.0, height: 32.0)
let modeSelectorFrame = CGRect(origin: CGPoint(x: floor((ChatMessagePollAddOptionNode.leftInset - modeSelectorSize.width) * 0.5) - 2.0, y: floor((size.height - modeSelectorSize.height) * 0.5)), size: modeSelectorSize)
var modeSelectorFrame = CGRect(origin: CGPoint(x: floor((ChatMessagePollAddOptionNode.leftInset - modeSelectorSize.width) * 0.5) - 2.0, y: floor((size.height - modeSelectorSize.height) * 0.5)), size: modeSelectorSize)
let animationName = self.currentFocusedTextInputIsMedia ? "input_anim_smileToKey" : "input_anim_keyToSmile"
if let currentModeSelectorAnimationName = self.currentModeSelectorAnimationName, currentModeSelectorAnimationName != animationName {
playAnimation = true
}
self.currentModeSelectorAnimationName = animationName
if self.currentFocusedTextInputIsMedia {
modeSelectorFrame = modeSelectorFrame.offsetBy(dx: 3.0, dy: 0.0)
}
let modeSelector: ComponentView<Empty>
if let current = self.modeSelector {
@ -1622,7 +1636,7 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
transition: animated ? .easeInOut(duration: 0.2) : .immediate,
component: AnyComponent(PlainButtonComponent(
content: AnyComponent(LottieComponent(
content: LottieComponent.AppBundleContent(name: "input_anim_keyToSmile"),
content: LottieComponent.AppBundleContent(name: animationName),
color: secondaryTextColor,
size: modeSelectorSize
)),
@ -1640,9 +1654,20 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
if modeSelectorView.superview == nil {
self.view.addSubview(modeSelectorView)
}
modeSelectorView.frame = modeSelectorFrame
if playAnimation {
let transition = ComponentTransition(animation: .curve(duration: animationName == "input_anim_smileToKey" ? 0.32 : 0.26, curve: .easeInOut))
transition.setFrame(view: modeSelectorView, frame: modeSelectorFrame)
} else {
modeSelectorView.frame = modeSelectorFrame
}
modeSelectorView.alpha = 1.0
modeSelectorView.transform = .identity
if let animationView = modeSelectorView.contentView as? LottieComponent.View {
if playAnimation {
animationView.playOnce()
}
}
}
} else if let modeSelector = self.modeSelector {
self.modeSelector = nil
@ -2090,6 +2115,9 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
return
}
self.newOptionIsFocused = focus
if !focus {
item.controllerInteraction.focusedTextInputIsMedia = false
}
item.controllerInteraction.updatePresentationState { state in
if focus {
if state.focusedPollAddOptionMessageId == item.message.id {
@ -2122,15 +2150,21 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
guard let item = self.item else {
return
}
item.controllerInteraction.updatePresentationState { state in
return state.updatedInputMode({ inputMode in
item.controllerInteraction.updatePresentationState { [weak item] state in
var focusedTextInputIsMedia = false
let updatedState = state.updatedInputMode({ inputMode in
if case .media = inputMode {
return .text
} else {
focusedTextInputIsMedia = true
return .media(mode: .other, expanded: .none, focused: true)
}
})
item?.controllerInteraction.focusedTextInputIsMedia = focusedTextInputIsMedia
return updatedState
}
self.requestNewOptionLayoutUpdate()
}
private func openNewOptionAttachment() {
@ -2707,7 +2741,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
let displayAddOption = poll.openAnswers && !isClosed && !hasVoted && poll.pollId.namespace == Namespaces.Media.CloudPoll
if displayAddOption {
let addOptionResult = makeAddOptionLayout(item.context, item.presentationData, item.presentationData.strings, incoming, currentNewOptionText, currentNewOptionAttachment, constrainedSize.width - layoutConstants.bubble.borderInset * 2.0)
let addOptionResult = makeAddOptionLayout(item.context, item.presentationData, item.presentationData.strings, incoming, item.controllerInteraction.focusedTextInputIsMedia, currentNewOptionText, currentNewOptionAttachment, constrainedSize.width - layoutConstants.bubble.borderInset * 2.0)
boundingSize.width = max(boundingSize.width, addOptionResult.minimumWidth + layoutConstants.bubble.borderInset * 2.0)
addOptionFinalizeLayout = addOptionResult.layout
}

View file

@ -325,7 +325,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
public var chatIsRotated: Bool = true
public var canReadHistory: Bool = false
public var summarizedMessageIds: Set<MessageId> = Set()
public var focusedTextInputIsMedia: Bool = false
private var isOpeningMediaValue: Bool = false
public var isOpeningMedia: Bool {

View file

@ -370,6 +370,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
private var hasRecentGifsDisposable: Disposable?
private let opaqueTopPanelBackground: Bool
private let useOpaqueTheme: Bool
private let displayBottomPanel: Bool
private struct EmojiSearchResult {
var groups: [EmojiPagerContentComponent.ItemGroup]
@ -484,13 +485,14 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|> distinctUntilChanged
}
public init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal<InputData, NoError>, defaultToEmojiTab: Bool, opaqueTopPanelBackground: Bool = false, useOpaqueTheme: Bool = false, interaction: ChatEntityKeyboardInputNode.Interaction?, chatPeerId: PeerId?, stateContext: StateContext?, forceHasPremium: Bool = false) {
public init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal<InputData, NoError>, defaultToEmojiTab: Bool, opaqueTopPanelBackground: Bool = false, useOpaqueTheme: Bool = false, interaction: ChatEntityKeyboardInputNode.Interaction?, chatPeerId: PeerId?, stateContext: StateContext?, forceHasPremium: Bool = false, displayBottomPanel: Bool = true) {
self.context = context
self.currentInputData = currentInputData
self.defaultToEmojiTab = defaultToEmojiTab
self.opaqueTopPanelBackground = opaqueTopPanelBackground
self.useOpaqueTheme = useOpaqueTheme
self.stateContext = stateContext
self.displayBottomPanel = displayBottomPanel
self.interaction = interaction
@ -2162,10 +2164,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
emoji: inputData.emoji.flatMap { emoji in
return emoji.withUpdatedItemGroups(panelItemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: emoji.panelItemGroups), contentItemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: emoji.contentItemGroups), itemContentUniqueId: emoji.itemContentUniqueId, emptySearchResults: emoji.emptySearchResults, searchState: emoji.searchState)
},
stickers: inputData.stickers.flatMap { stickers in
stickers: !self.displayBottomPanel ? nil : inputData.stickers.flatMap { stickers in
return stickers.withUpdatedItemGroups(panelItemGroups: self.processStableItemGroupList(category: .stickers, itemGroups: stickers.panelItemGroups), contentItemGroups: self.processStableItemGroupList(category: .stickers, itemGroups: stickers.contentItemGroups), itemContentUniqueId: stickers.itemContentUniqueId, emptySearchResults: nil, searchState: stickers.searchState)
},
gifs: inputData.gifs,
gifs: !self.displayBottomPanel ? nil : inputData.gifs,
availableGifSearchEmojies: inputData.availableGifSearchEmojies
)
}

View file

@ -1503,11 +1503,12 @@ public final class TextFieldComponent: Component {
environment: {},
containerSize: textFrame.size
)
let placeholderFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: textFrame.minY + floor((textFrame.height - placeholderSize.height) * 0.5) - 1.0), size: placeholderSize)
let placeholderFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((textFrame.height - placeholderSize.height) * 0.5) - 1.0), size: placeholderSize)
if let placeholderView = placeholder.view {
if placeholderView.superview == nil {
placeholderView.isUserInteractionEnabled = false
placeholderView.layer.anchorPoint = CGPoint()
self.insertSubview(placeholderView, belowSubview: self.textView)
self.textView.insertSubview(placeholderView, at: 0)
}
placeholderTransition.setPosition(view: placeholderView, position: placeholderFrame.origin)
placeholderView.bounds = CGRect(origin: CGPoint(), size: placeholderFrame.size)

View file

@ -4130,16 +4130,13 @@ extension ChatControllerImpl {
guard let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction else {
return
}
if let textFieldView = strongSelf.chatDisplayNode.chatPresentationInterfaceStateTextFieldView(strongSelf.presentationInterfaceState) {
textFieldView.insertText(text)
return
}
if !strongSelf.chatDisplayNode.isTextInputPanelActive {
return
}
interfaceInteraction.updateTextInputStateAndMode { textInputState, inputMode in
let inputText = NSMutableAttributedString(attributedString: textInputState.inputText)
@ -4163,6 +4160,10 @@ extension ChatControllerImpl {
guard let strongSelf = self else {
return
}
if let textFieldView = strongSelf.chatDisplayNode.chatPresentationInterfaceStateTextFieldView(strongSelf.presentationInterfaceState) {
textFieldView.deleteBackward()
return
}
if !strongSelf.chatDisplayNode.isTextInputPanelActive {
return
}

View file

@ -28,6 +28,15 @@ extension ChatControllerImpl {
let pollOption = poll.options[pollOptionIndex]
var selectedOptions: [Data] = []
if let voters = poll.results.voters {
for voter in voters {
if voter.selected {
selectedOptions.append(voter.opaqueIdentifier)
}
}
}
let _ = (contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: self.presentationInterfaceState, context: self.context, messages: [message], controllerInteraction: self.controllerInteraction, selectAll: false, interfaceInteraction: self.interfaceInteraction, messageNode: params.messageNode as? ChatMessageItemView)
|> deliverOnMainQueue).start(next: { [weak self] actions in
guard let self else {
@ -35,36 +44,34 @@ extension ChatControllerImpl {
}
var items: [ContextMenuItem] = []
// if canMark {
// items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Todo_ContextMenu_CheckTask, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
// guard let self else {
// return
// }
//
// if !self.context.isPremium {
// f(.default)
// let controller = UndoOverlayController(
// presentationData: self.presentationData,
// content: .premiumPaywall(title: nil, text: self.presentationData.strings.Chat_Todo_PremiumRequired, customUndoText: nil, timeout: nil, linkAction: nil),
// action: { [weak self] action in
// guard let self else {
// return false
// }
// if case .info = action {
// let controller = self.context.sharedContext.makePremiumIntroController(context: context, source: .presence, forceDark: false, dismissed: nil)
// self.push(controller)
// }
// return false
// }
// )
// self.present(controller, in: .current)
// } else {
// c?.dismiss(completion: {
// let _ = self.context.engine.messages.requestUpdateTodoMessageItems(messageId: message.id, completedIds: [todoItemId], incompletedIds: []).start()
// })
// }
// })))
// }
if !poll.isClosed && (selectedOptions.isEmpty || !poll.revotingDisabled) {
if selectedOptions.contains(pollOption.opaqueIdentifier) {
items.append(.action(ContextMenuActionItem(text: "Retract Vote", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unvote"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
guard let self else {
return
}
c?.dismiss(result: .default, completion: {
var updatedOptions = selectedOptions
updatedOptions.removeAll(where: { $0 == pollOption.opaqueIdentifier } )
self.controllerInteraction?.requestSelectMessagePollOptions(message.id, updatedOptions)
})
})))
} else {
items.append(.action(ContextMenuActionItem(text: "Vote", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/StopPoll"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
guard let self else {
return
}
c?.dismiss(result: .default, completion: {
var updatedOptions = selectedOptions
if !poll.kind.multipleAnswers {
updatedOptions = []
}
updatedOptions.append(pollOption.opaqueIdentifier)
self.controllerInteraction?.requestSelectMessagePollOptions(message.id, updatedOptions)
})
})))
}
}
//TODO:localize
if canReplyInChat(self.presentationInterfaceState, accountPeerId: self.context.account.peerId) {
@ -139,6 +146,18 @@ extension ChatControllerImpl {
})))
}
if pollOption.date != nil {
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Remove", textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] _, f in
f(.default)
guard let self else {
return
}
let _ = self.context.engine.messages.deletePollOption(messageId: message.id, opaqueIdentifier: pollOption.opaqueIdentifier).start()
})))
}
self.canReadHistory.set(false)
//TODO:localize

View file

@ -3733,7 +3733,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
//TODO:localize
let controller = UndoOverlayController(
presentationData: strongSelf.presentationData,
content: .universal(animation: "anim_timer", scale: 0.066, colors: [:], title: nil, text: "Results will appear after the poll ends", customUndoText: nil, timeout: nil),
content: .universal(animation: "anim_timer", scale: 0.06, colors: [:], title: nil, text: "Results will appear after the poll ends", customUndoText: nil, timeout: nil),
action: { _ in return true }
)
strongSelf.present(controller, in: .current)

View file

@ -3681,18 +3681,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
var waitForKeyboardLayout = false
var effectiveTextView: UITextView?
let customTextView = self.chatPresentationInterfaceStateTextFieldView(chatPresentationInterfaceState)
if let customTextView {
effectiveTextView = customTextView.inputTextView
} else if let mainTextView = self.textInputPanelNode?.textInputNode?.textView {
effectiveTextView = mainTextView
}
if let textView = effectiveTextView {
if let textView = self.textInputPanelNode?.textInputNode?.textView {
let updatedInputView = self.chatPresentationInterfaceStateInputView(chatPresentationInterfaceState)
if textView.inputView !== updatedInputView {
textView.inputView = updatedInputView
if textView.isFirstResponder {
if let customTextView {
if customTextView.isActive {
if self.chatPresentationInterfaceStateRequiresInputFocus(chatPresentationInterfaceState), let validLayout = self.validLayout {
if case .compact = validLayout.0.metrics.widthClass {
waitForKeyboardLayout = true
@ -3700,7 +3693,20 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
waitForKeyboardLayout = true
}
}
textView.reloadInputViews()
}
} else {
if textView.inputView !== updatedInputView {
textView.inputView = updatedInputView
if textView.isFirstResponder {
if self.chatPresentationInterfaceStateRequiresInputFocus(chatPresentationInterfaceState), let validLayout = self.validLayout {
if case .compact = validLayout.0.metrics.widthClass {
waitForKeyboardLayout = true
} else if let inputHeight = validLayout.0.inputHeight, inputHeight > 100.0 {
waitForKeyboardLayout = true
}
}
textView.reloadInputViews()
}
}
}
}
@ -3888,10 +3894,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
context: self.context,
currentInputData: inputMediaNodeData,
updatedInputData: self.inputMediaNodeDataPromise.get(),
defaultToEmojiTab: !self.chatPresentationInterfaceState.interfaceState.effectiveInputState.inputText.string.isEmpty || self.chatPresentationInterfaceState.interfaceState.forwardMessageIds != nil || self.openStickersBeginWithEmoji,
defaultToEmojiTab: !self.chatPresentationInterfaceState.interfaceState.effectiveInputState.inputText.string.isEmpty || self.chatPresentationInterfaceState.interfaceState.forwardMessageIds != nil || self.openStickersBeginWithEmoji || self.chatPresentationInterfaceState.focusedPollAddOptionMessageId != nil,
interaction: ChatEntityKeyboardInputNode.Interaction(chatControllerInteraction: self.controllerInteraction, panelInteraction: interfaceInteraction),
chatPeerId: peerId,
stateContext: self.inputMediaNodeStateContext
stateContext: self.inputMediaNodeStateContext,
displayBottomPanel: self.chatPresentationInterfaceState.focusedPollAddOptionMessageId == nil
)
self.openStickersBeginWithEmoji = false