Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,278 @@
import Foundation
import UIKit
import TelegramCore
import Postbox
import Display
import AccountContext
import Emoji
import ChatInterfaceState
import ChatPresentationInterfaceState
import SwiftSignalKit
import TextFormat
import ChatContextQuery
import ChatTextInputPanelNode
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 let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let mainChannel = chatPresentationInterfaceState.renderedPeer?.chatOrMonoforumMainPeer as? TelegramChannel, (!mainChannel.hasPermission(.manageDirect) || chatPresentationInterfaceState.chatLocation.threadId != nil) {
if chatPresentationInterfaceState.interfaceState.postSuggestionState == nil {
accessoryItems.append(.suggestPost)
}
}
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)
}
}
}