import Foundation import UIKit import TelegramCore import Postbox import Display import AccountContext import Emoji import ChatInterfaceState import ChatPresentationInterfaceState import SwiftSignalKit import TextFormat import ChatContextQuery func serviceTasksForChatPresentationIntefaceState(context: AccountContext, chatPresentationInterfaceState: ChatPresentationInterfaceState, updateState: @escaping ((ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) -> Void) -> [AnyHashable: () -> Disposable] { var missingEmoji = Set() let inputText = chatPresentationInterfaceState.interfaceState.composeInputState.inputText inputText.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: NSRange(location: 0, length: inputText.length), using: { value, _, _ in if let value = value as? ChatTextInputTextCustomEmojiAttribute { if value.file == nil { missingEmoji.insert(value.fileId) } } }) var result: [AnyHashable: () -> Disposable] = [:] for id in missingEmoji { result["emoji-\(id)"] = { return (context.engine.stickers.resolveInlineStickers(fileIds: [id]) |> deliverOnMainQueue).startStrict(next: { result in if let file = result[id] { updateState({ state -> ChatPresentationInterfaceState in return state.updatedInterfaceState { interfaceState -> ChatInterfaceState in var inputState = interfaceState.composeInputState let text = NSMutableAttributedString(attributedString: inputState.inputText) inputState.inputText.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: NSRange(location: 0, length: inputText.length), using: { value, range, _ in if let value = value as? ChatTextInputTextCustomEmojiAttribute { if value.fileId == id { text.removeAttribute(ChatTextInputAttributes.customEmoji, range: range) text.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file), range: range) } } }) inputState.inputText = text return interfaceState.withUpdatedComposeInputState(inputState) } }) } }) } } return result } func inputContextQueriesForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState) -> [ChatPresentationInputQuery] { if case let .customChatContents(customChatContents) = chatPresentationInterfaceState.subject { switch customChatContents.kind { case .hashTagSearch: return [] case .quickReplyMessageInput: break case .businessLinkSetup: return [] case .postSuggestions: return [] } } let inputState = chatPresentationInterfaceState.interfaceState.effectiveInputState let inputString: NSString = inputState.inputText.string as NSString var result: [ChatPresentationInputQuery] = [] for (possibleQueryRange, possibleTypes, additionalStringRange) in textInputStateContextQueryRangeAndType(inputState) { let query = inputString.substring(with: possibleQueryRange) if possibleTypes == [.emoji] { result.append(.emoji(query.basicEmoji.0)) } else if possibleTypes == [.hashtag] { result.append(.hashtag(query)) } else if possibleTypes == [.mention] { var types: ChatInputQueryMentionTypes = [.members] if possibleQueryRange.lowerBound == 1 { types.insert(.contextBots) } result.append(.mention(query: query, types: types)) } else if possibleTypes == [.command] { result.append(.command(query)) } else if possibleTypes == [.contextRequest], let additionalStringRange = additionalStringRange { let additionalString = inputString.substring(with: additionalStringRange) result.append(.contextRequest(addressName: query, query: additionalString)) } else if possibleTypes == [.emojiSearch], !query.isEmpty, let inputLanguage = chatPresentationInterfaceState.interfaceState.inputLanguage { result.append(.emojiSearch(query: query, languageCode: inputLanguage, range: possibleQueryRange)) } } return result } func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext) -> ChatTextInputPanelState { var contextPlaceholder: NSAttributedString? loop: for (_, result) in chatPresentationInterfaceState.inputQueryResults { if case let .contextRequestResult(peer, _) = result, case let .user(botUser) = peer, let botInfo = botUser.botInfo, let inlinePlaceholder = botInfo.inlinePlaceholder { let inputQueries = inputContextQueriesForChatPresentationIntefaceState(chatPresentationInterfaceState) for inputQuery in inputQueries { if case let .contextRequest(addressName, query) = inputQuery, query.isEmpty { let baseFontSize: CGFloat = max(chatTextInputMinFontSize, chatPresentationInterfaceState.fontSize.baseDisplaySize) let string = NSMutableAttributedString() string.append(NSAttributedString(string: "@" + addressName, font: Font.regular(baseFontSize), textColor: UIColor.clear)) string.append(NSAttributedString(string: " " + inlinePlaceholder, font: Font.regular(baseFontSize), textColor: chatPresentationInterfaceState.theme.chat.inputPanel.inputPlaceholderColor)) contextPlaceholder = string } } break loop } } var currentAutoremoveTimeout: Int32? = chatPresentationInterfaceState.autoremoveTimeout var canSetupAutoremoveTimeout = false var canSendTextMessages = true var accessoryItems: [ChatTextInputAccessoryItem] = [] if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat { var extendedSearchLayout = false loop: for (_, result) in chatPresentationInterfaceState.inputQueryResults { if case let .contextRequestResult(peer, _) = result, peer != nil { extendedSearchLayout = true break loop } } if !extendedSearchLayout { currentAutoremoveTimeout = peer.messageAutoremoveTimeout canSetupAutoremoveTimeout = true } } else if let group = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramGroup { if !group.hasBannedPermission(.banChangeInfo) { canSetupAutoremoveTimeout = true } canSendTextMessages = !group.hasBannedPermission(.banSendText) } else if let user = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser { if user.botInfo == nil { canSetupAutoremoveTimeout = true } } else if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel { if channel.hasPermission(.changeInfo) { canSetupAutoremoveTimeout = true } canSendTextMessages = channel.hasBannedPermission(.banSendText) == nil } if canSetupAutoremoveTimeout { if case .scheduledMessages = chatPresentationInterfaceState.subject { } else if chatPresentationInterfaceState.renderedPeer?.peerId != context.account.peerId { if currentAutoremoveTimeout != nil || chatPresentationInterfaceState.renderedPeer?.peer is TelegramSecretChat { accessoryItems.append(.messageAutoremoveTimeout(currentAutoremoveTimeout)) } } } switch chatPresentationInterfaceState.inputMode { case .media: accessoryItems.append(.input(isEnabled: true, inputMode: .keyboard)) return ChatTextInputPanelState(accessoryItems: accessoryItems, contextPlaceholder: contextPlaceholder, mediaRecordingState: chatPresentationInterfaceState.inputTextPanelState.mediaRecordingState) case .inputButtons: return ChatTextInputPanelState(accessoryItems: [.botInput(isEnabled: true, inputMode: .keyboard)], contextPlaceholder: contextPlaceholder, mediaRecordingState: chatPresentationInterfaceState.inputTextPanelState.mediaRecordingState) case .none, .text: if let _ = chatPresentationInterfaceState.interfaceState.editMessage { accessoryItems.append(.input(isEnabled: true, inputMode: .emoji)) return ChatTextInputPanelState(accessoryItems: accessoryItems, contextPlaceholder: contextPlaceholder, mediaRecordingState: chatPresentationInterfaceState.inputTextPanelState.mediaRecordingState) } else { var accessoryItems: [ChatTextInputAccessoryItem] = [] let isTextEmpty = chatPresentationInterfaceState.interfaceState.composeInputState.inputText.length == 0 let hasForward = chatPresentationInterfaceState.interfaceState.forwardMessageIds != nil var extendedSearchLayout = false loop: for (_, result) in chatPresentationInterfaceState.inputQueryResults { if case let .contextRequestResult(peer, _) = result, peer != nil { extendedSearchLayout = true break loop } } if !extendedSearchLayout { if case .scheduledMessages = chatPresentationInterfaceState.subject { } else if chatPresentationInterfaceState.renderedPeer?.peerId != context.account.peerId { if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat, chatPresentationInterfaceState.interfaceState.composeInputState.inputText.length == 0 { accessoryItems.append(.messageAutoremoveTimeout(peer.messageAutoremoveTimeout)) } else if currentAutoremoveTimeout != nil && chatPresentationInterfaceState.interfaceState.composeInputState.inputText.length == 0 { accessoryItems.append(.messageAutoremoveTimeout(currentAutoremoveTimeout)) } } } if case .scheduledMessages = chatPresentationInterfaceState.subject { } else { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) var showPremiumGift = false if !premiumConfiguration.isPremiumDisabled && chatPresentationInterfaceState.disallowedGifts != TelegramDisallowedGifts.All { if chatPresentationInterfaceState.alwaysShowGiftButton { showPremiumGift = true } else if chatPresentationInterfaceState.hasBirthdayToday { showPremiumGift = true } else if premiumConfiguration.showPremiumGiftInAttachMenu && premiumConfiguration.showPremiumGiftInTextField { showPremiumGift = true } } if isTextEmpty, showPremiumGift, let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, !peer.isDeleted && peer.botInfo == nil && !peer.flags.contains(.isSupport) { //&& chatPresentationInterfaceState.suggestPremiumGift { accessoryItems.append(.gift) } } if isTextEmpty && chatPresentationInterfaceState.hasScheduledMessages && !hasForward { accessoryItems.append(.scheduledMessages) } var stickersEnabled = true var stickersAreEmoji = !isTextEmpty if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel { if isTextEmpty, case .broadcast = peer.info, canSendMessagesToPeer(peer) { accessoryItems.append(.silentPost(chatPresentationInterfaceState.interfaceState.silentPosting)) } if let boostsToUnrestrict = chatPresentationInterfaceState.boostsToUnrestrict, boostsToUnrestrict > 0 { } else { if peer.hasBannedPermission(.banSendStickers) != nil { stickersEnabled = false } } } else if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramGroup { if peer.hasBannedPermission(.banSendStickers) { stickersEnabled = false } } if case let .customChatContents(customChatContents) = chatPresentationInterfaceState.subject { switch customChatContents.kind { case .hashTagSearch: break case .quickReplyMessageInput: break case .businessLinkSetup: stickersEnabled = false case .postSuggestions: break } } if isTextEmpty && chatPresentationInterfaceState.hasBots && chatPresentationInterfaceState.hasBotCommands && !hasForward { accessoryItems.append(.commands) } if !canSendTextMessages { if stickersEnabled && !stickersAreEmoji && !hasForward { accessoryItems.append(.input(isEnabled: true, inputMode: .stickers)) } } else { stickersAreEmoji = stickersAreEmoji || hasForward if stickersEnabled { accessoryItems.append(.input(isEnabled: true, inputMode: stickersAreEmoji ? .emoji : .stickers)) } else { accessoryItems.append(.input(isEnabled: true, inputMode: .emoji)) } } if isTextEmpty, let message = chatPresentationInterfaceState.keyboardButtonsMessage, let _ = message.visibleButtonKeyboardMarkup, chatPresentationInterfaceState.interfaceState.messageActionsState.dismissedButtonKeyboardMessageId != message.id { accessoryItems.append(.botInput(isEnabled: true, inputMode: .bot)) } return ChatTextInputPanelState(accessoryItems: accessoryItems, contextPlaceholder: contextPlaceholder, mediaRecordingState: chatPresentationInterfaceState.inputTextPanelState.mediaRecordingState) } } }