Swiftgram/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift
2025-03-21 18:41:49 +04:00

272 lines
15 KiB
Swift

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<Int64>()
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 []
}
}
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
}
}
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)
}
}
}