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,31 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatPresentationInterfaceState",
module_name = "ChatPresentationInterfaceState",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/TelegramCore:TelegramCore",
"//submodules/AccountContext:AccountContext",
"//submodules/ContextUI:ContextUI",
"//submodules/ChatInterfaceState:ChatInterfaceState",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/ChatContextQuery",
"//submodules/TooltipUI",
"//submodules/AudioWaveform",
"//submodules/UndoUI",
"//submodules/TextFormat",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,43 @@
import Foundation
import UIKit
import TelegramCore
public struct MessageMediaEditingOptions: OptionSet {
public var rawValue: Int32
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public static let imageOrVideo = MessageMediaEditingOptions(rawValue: 1 << 0)
public static let file = MessageMediaEditingOptions(rawValue: 1 << 1)
}
public enum ChatEditInterfaceMessageStateContent: Equatable {
case plaintext
case media(mediaOptions: MessageMediaEditingOptions)
}
public final class ChatEditInterfaceMessageState: Equatable {
public let content: ChatEditInterfaceMessageStateContent
public let mediaReference: AnyMediaReference?
public init(content: ChatEditInterfaceMessageStateContent, mediaReference: AnyMediaReference?) {
self.content = content
self.mediaReference = mediaReference
}
public static func ==(lhs: ChatEditInterfaceMessageState, rhs: ChatEditInterfaceMessageState) -> Bool {
if lhs.content != rhs.content {
return false
}
if let lhsMedia = lhs.mediaReference, let rhsMedia = rhs.mediaReference {
if !lhsMedia.media.isEqual(to: rhsMedia.media) {
return false
}
} else if (lhs.mediaReference != nil) != (rhs.mediaReference != nil) {
return false
}
return true
}
}
@@ -0,0 +1,57 @@
import Foundation
import Postbox
import TelegramCore
import TelegramUIPreferences
public struct ChatInterfaceStickerSettings: Equatable {
public init() {
}
public init(stickerSettings: StickerSettings) {
}
public static func ==(lhs: ChatInterfaceStickerSettings, rhs: ChatInterfaceStickerSettings) -> Bool {
return true
}
}
public enum ChatMediaInputGifMode: Equatable {
case recent
case trending
case emojiSearch(String)
}
public final class ChatMediaInputNodeInteraction {
public let navigateToCollectionId: (ItemCollectionId) -> Void
public let navigateBackToStickers: () -> Void
public let setGifMode: (ChatMediaInputGifMode) -> Void
public let openSettings: () -> Void
public let openTrending: (ItemCollectionId?) -> Void
public let dismissTrendingPacks: ([ItemCollectionId]) -> Void
public let toggleSearch: (Bool, ChatMediaInputSearchMode?, String) -> Void
public let openPeerSpecificSettings: () -> Void
public let dismissPeerSpecificSettings: () -> Void
public let clearRecentlyUsedStickers: () -> Void
public var stickerSettings: ChatInterfaceStickerSettings?
public var highlightedStickerItemCollectionId: ItemCollectionId?
public var highlightedItemCollectionId: ItemCollectionId?
public var highlightedGifMode: ChatMediaInputGifMode = .recent
public var previewedStickerPackItemFile: TelegramMediaFile?
public var appearanceTransition: CGFloat = 1.0
public var displayStickerPlaceholder = true
public var displayStickerPackManageControls = true
public init(navigateToCollectionId: @escaping (ItemCollectionId) -> Void, navigateBackToStickers: @escaping () -> Void, setGifMode: @escaping (ChatMediaInputGifMode) -> Void, openSettings: @escaping () -> Void, openTrending: @escaping (ItemCollectionId?) -> Void, dismissTrendingPacks: @escaping ([ItemCollectionId]) -> Void, toggleSearch: @escaping (Bool, ChatMediaInputSearchMode?, String) -> Void, openPeerSpecificSettings: @escaping () -> Void, dismissPeerSpecificSettings: @escaping () -> Void, clearRecentlyUsedStickers: @escaping () -> Void) {
self.navigateToCollectionId = navigateToCollectionId
self.navigateBackToStickers = navigateBackToStickers
self.setGifMode = setGifMode
self.openSettings = openSettings
self.openTrending = openTrending
self.dismissTrendingPacks = dismissTrendingPacks
self.toggleSearch = toggleSearch
self.openPeerSpecificSettings = openPeerSpecificSettings
self.dismissPeerSpecificSettings = dismissPeerSpecificSettings
self.clearRecentlyUsedStickers = clearRecentlyUsedStickers
}
}
@@ -0,0 +1,593 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Postbox
import SwiftSignalKit
import TelegramCore
import Display
import AccountContext
import ContextUI
import TooltipUI
import UndoUI
import TextFormat
public enum ChatLoadingMessageSubject {
case generic
case pinnedMessage
}
public enum ChatFinishMediaRecordingAction {
case dismiss
case preview
case pause
case send(viewOnce: Bool)
}
public final class ChatPanelInterfaceInteractionStatuses {
public let editingMessage: Signal<Float?, NoError>
public let startingBot: Signal<Bool, NoError>
public let unblockingPeer: Signal<Bool, NoError>
public let searching: Signal<Bool, NoError>
public let loadingMessage: Signal<ChatLoadingMessageSubject?, NoError>
public let inlineSearch: Signal<Bool, NoError>
public init(editingMessage: Signal<Float?, NoError>, startingBot: Signal<Bool, NoError>, unblockingPeer: Signal<Bool, NoError>, searching: Signal<Bool, NoError>, loadingMessage: Signal<ChatLoadingMessageSubject?, NoError>, inlineSearch: Signal<Bool, NoError>) {
self.editingMessage = editingMessage
self.startingBot = startingBot
self.unblockingPeer = unblockingPeer
self.searching = searching
self.loadingMessage = loadingMessage
self.inlineSearch = inlineSearch
}
}
public enum ChatPanelSearchNavigationAction {
case earlier
case later
case index(Int)
}
public enum ChatPanelRestrictionInfoSubject {
case mediaRecording
case stickers
case premiumVoiceMessages
}
public enum ChatPanelRestrictionInfoDisplayType {
case tooltip
case alert
}
public enum ChatTranslationDisplayType {
case original
case translated
}
public final class ChatPanelInterfaceInteraction {
public enum OpenSuggestPostMode {
case `default`
case editMessage
case editTime
case editPrice
}
public let setupReplyMessage: (MessageId?, Int32?, @escaping (ContainedViewLayoutTransition, @escaping () -> Void) -> Void) -> Void
public let setupEditMessage: (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void
public let beginMessageSelection: ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void
public let cancelMessageSelection: (ContainedViewLayoutTransition) -> Void
public let deleteSelectedMessages: () -> Void
public let reportSelectedMessages: () -> Void
public let reportMessages: ([Message], ContextControllerProtocol?) -> Void
public let blockMessageAuthor: (Message, ContextControllerProtocol?) -> Void
public let deleteMessages: ([Message], ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void
public let forwardSelectedMessages: () -> Void
public let forwardCurrentForwardMessages: () -> Void
public let forwardMessages: ([Message]) -> Void
public let updateForwardOptionsState: ((ChatInterfaceForwardOptionsState) -> ChatInterfaceForwardOptionsState) -> Void
public let presentForwardOptions: (UIView) -> Void
public let presentReplyOptions: (UIView) -> Void
public let presentLinkOptions: (UIView) -> Void
public let presentSuggestPostOptions: () -> Void
public let shareSelectedMessages: () -> Void
public let updateTextInputStateAndMode: (@escaping (ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void
public let updateInputModeAndDismissedButtonKeyboardMessageId: ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void
public let openStickers: () -> Void
public let editMessage: () -> Void
public let beginMessageSearch: (ChatSearchDomain, String) -> Void
public let dismissMessageSearch: () -> Void
public let updateMessageSearch: (String) -> Void
public let navigateMessageSearch: (ChatPanelSearchNavigationAction) -> Void
public let openSearchResults: () -> Void
public let openCalendarSearch: () -> Void
public let toggleMembersSearch: (Bool) -> Void
public let navigateToMessage: (MessageId, Bool, Bool, ChatLoadingMessageSubject) -> Void
public let navigateToChat: (PeerId) -> Void
public let navigateToProfile: (PeerId) -> Void
public let openPeerInfo: () -> Void
public let togglePeerNotifications: () -> Void
public let sendContextResult: (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool
public let sendBotCommand: (Peer, String) -> Void
public let sendShortcut: (Int32) -> Void
public let openEditShortcuts: () -> Void
public let sendBotStart: (String?) -> Void
public let botSwitchChatWithPayload: (PeerId, String) -> Void
public let beginMediaRecording: (Bool) -> Void
public let finishMediaRecording: (ChatFinishMediaRecordingAction) -> Void
public let stopMediaRecording: () -> Void
public let lockMediaRecording: () -> Void
public let resumeMediaRecording: () -> Void
public let deleteRecordedMedia: () -> Void
public let sendRecordedMedia: (Bool, Bool) -> Void
public let displayRestrictedInfo: (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void
public let displayVideoUnmuteTip: (CGPoint?) -> Void
public let switchMediaRecordingMode: () -> Void
public let setupMessageAutoremoveTimeout: () -> Void
public let sendSticker: (FileMediaReference, Bool, UIView, CGRect, CALayer?, [ItemCollectionId]) -> Bool
public let unblockPeer: () -> Void
public let pinMessage: (MessageId, ContextControllerProtocol?) -> Void
public let unpinMessage: (MessageId, Bool, ContextControllerProtocol?) -> Void
public let unpinAllMessages: () -> Void
public let openPinnedList: (MessageId) -> Void
public let shareAccountContact: () -> Void
public let reportPeer: () -> Void
public let presentPeerContact: () -> Void
public let dismissReportPeer: () -> Void
public let deleteChat: () -> Void
public let beginCall: (Bool) -> Void
public let toggleMessageStickerStarred: (MessageId) -> Void
public let presentController: (ViewController, Any?) -> Void
public let presentControllerInCurrent: (ViewController, Any?) -> Void
public let getNavigationController: () -> NavigationController?
public let presentGlobalOverlayController: (ViewController, Any?) -> Void
public let navigateFeed: () -> Void
public let openGrouping: () -> Void
public let toggleSilentPost: () -> Void
public let requestUnvoteInMessage: (MessageId) -> Void
public let requestStopPollInMessage: (MessageId) -> Void
public let updateInputLanguage: (@escaping (String?) -> String?) -> Void
public let unarchiveChat: () -> Void
public let openLinkEditing: () -> Void
public let displaySlowmodeTooltip: (UIView, CGRect) -> Void
public let displaySendMessageOptions: (ASDisplayNode, ContextGesture) -> Void
public let openScheduledMessages: () -> Void
public let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void
public let openPeersNearby: () -> Void
public let unarchivePeer: () -> Void
public let scrollToTop: () -> Void
public let viewReplies: (MessageId?, ChatReplyThreadMessage) -> Void
public let activatePinnedListPreview: (ASDisplayNode, ContextGesture) -> Void
public let editMessageMedia: (MessageId, Bool) -> Void
public let joinGroupCall: (CachedChannelData.ActiveCall) -> Void
public let presentInviteMembers: () -> Void
public let presentGigagroupHelp: () -> Void
public let openMonoforum: () -> Void
public let updateShowCommands: ((Bool) -> Bool) -> Void
public let updateShowSendAsPeers: ((Bool) -> Bool) -> Void
public let openInviteRequests: () -> Void
public let openSendAsPeer: (ASDisplayNode, ContextGesture?) -> Void
public let presentChatRequestAdminInfo: () -> Void
public let displayCopyProtectionTip: (UIView, Bool) -> Void
public let openWebView: (String, String, Bool, ChatOpenWebViewSource) -> Void
public let updateShowWebView: ((Bool) -> Bool) -> Void
public let insertText: (NSAttributedString) -> Void
public let backwardsDeleteText: () -> Void
public let restartTopic: () -> Void
public let toggleTranslation: (ChatTranslationDisplayType) -> Void
public let changeTranslationLanguage: (String) -> Void
public let addDoNotTranslateLanguage: (String) -> Void
public let hideTranslationPanel: () -> Void
public let openPremiumGift: () -> Void
public let openSuggestPost: (Message?, OpenSuggestPostMode) -> Void
public let openPremiumRequiredForMessaging: () -> Void
public let openStarsPurchase: (Int64?) -> Void
public let openMessagePayment: () -> Void
public let updateHistoryFilter: ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void
public let updateChatLocationThread: (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void
public let toggleChatSidebarMode: () -> Void
public let updateDisplayHistoryFilterAsList: (Bool) -> Void
public let openBoostToUnrestrict: () -> Void
public let updateRecordingTrimRange: (Double, Double, Bool, Bool) -> Void
public let dismissAllTooltips: () -> Void
public let editTodoMessage: (MessageId, Int32?, Bool) -> Void
public let dismissUrlPreview: () -> Void
public let dismissForwardMessages: () -> Void
public let dismissSuggestPost: () -> Void
public let displayUndo: (UndoOverlayContent) -> Void
public let sendEmoji: (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void
public let requestLayout: (ContainedViewLayoutTransition) -> Void
public let chatController: () -> ViewController?
public let statuses: ChatPanelInterfaceInteractionStatuses?
public init(
setupReplyMessage: @escaping (MessageId?, Int32?, @escaping (ContainedViewLayoutTransition, @escaping () -> Void) -> Void) -> Void,
setupEditMessage: @escaping (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void,
beginMessageSelection: @escaping ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void,
cancelMessageSelection: @escaping (ContainedViewLayoutTransition) -> Void,
deleteSelectedMessages: @escaping () -> Void,
reportSelectedMessages: @escaping () -> Void,
reportMessages: @escaping ([Message], ContextControllerProtocol?) -> Void,
blockMessageAuthor: @escaping (Message, ContextControllerProtocol?) -> Void,
deleteMessages: @escaping ([Message], ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void,
forwardSelectedMessages: @escaping () -> Void,
forwardCurrentForwardMessages: @escaping () -> Void,
forwardMessages: @escaping ([Message]) -> Void,
updateForwardOptionsState: @escaping ((ChatInterfaceForwardOptionsState) -> ChatInterfaceForwardOptionsState) -> Void,
presentForwardOptions: @escaping (UIView) -> Void,
presentReplyOptions: @escaping (UIView) -> Void,
presentLinkOptions: @escaping (UIView) -> Void,
presentSuggestPostOptions: @escaping () -> Void,
shareSelectedMessages: @escaping () -> Void,
updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void,
updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void,
openStickers: @escaping () -> Void,
editMessage: @escaping () -> Void,
beginMessageSearch: @escaping (ChatSearchDomain, String) -> Void,
dismissMessageSearch: @escaping () -> Void,
updateMessageSearch: @escaping (String) -> Void,
openSearchResults: @escaping () -> Void,
navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void,
openCalendarSearch: @escaping () -> Void,
toggleMembersSearch: @escaping (Bool) -> Void,
navigateToMessage: @escaping (MessageId, Bool, Bool, ChatLoadingMessageSubject) -> Void,
navigateToChat: @escaping (PeerId) -> Void,
navigateToProfile: @escaping (PeerId) -> Void,
openPeerInfo: @escaping () -> Void,
togglePeerNotifications: @escaping () -> Void,
sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool,
sendBotCommand: @escaping (Peer, String) -> Void,
sendShortcut: @escaping (Int32) -> Void,
openEditShortcuts: @escaping () -> Void,
sendBotStart: @escaping (String?) -> Void,
botSwitchChatWithPayload: @escaping (PeerId, String) -> Void,
beginMediaRecording: @escaping (Bool) -> Void,
finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void,
stopMediaRecording: @escaping () -> Void,
lockMediaRecording: @escaping () -> Void,
resumeMediaRecording: @escaping () -> Void,
deleteRecordedMedia: @escaping () -> Void,
sendRecordedMedia: @escaping (Bool, Bool) -> Void,
displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void,
displayVideoUnmuteTip: @escaping (CGPoint?) -> Void,
switchMediaRecordingMode: @escaping () -> Void,
setupMessageAutoremoveTimeout: @escaping () -> Void,
sendSticker: @escaping (FileMediaReference, Bool, UIView, CGRect, CALayer?, [ItemCollectionId]) -> Bool,
unblockPeer: @escaping () -> Void,
pinMessage: @escaping (MessageId, ContextControllerProtocol?) -> Void,
unpinMessage: @escaping (MessageId, Bool, ContextControllerProtocol?) -> Void,
unpinAllMessages: @escaping () -> Void,
openPinnedList: @escaping (MessageId) -> Void,
shareAccountContact: @escaping () -> Void,
reportPeer: @escaping () -> Void,
presentPeerContact: @escaping () -> Void,
dismissReportPeer: @escaping () -> Void,
deleteChat: @escaping () -> Void,
beginCall: @escaping (Bool) -> Void,
toggleMessageStickerStarred: @escaping (MessageId) -> Void,
presentController: @escaping (ViewController, Any?) -> Void,
presentControllerInCurrent: @escaping (ViewController, Any?) -> Void,
getNavigationController: @escaping () -> NavigationController?,
presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void,
navigateFeed: @escaping () -> Void,
openGrouping: @escaping () -> Void,
toggleSilentPost: @escaping () -> Void,
requestUnvoteInMessage: @escaping (MessageId) -> Void,
requestStopPollInMessage: @escaping (MessageId) -> Void,
updateInputLanguage: @escaping ((String?) -> String?) -> Void,
unarchiveChat: @escaping () -> Void,
openLinkEditing: @escaping () -> Void,
displaySlowmodeTooltip: @escaping (UIView, CGRect) -> Void,
displaySendMessageOptions: @escaping (ASDisplayNode, ContextGesture) -> Void,
openScheduledMessages: @escaping () -> Void,
openPeersNearby: @escaping () -> Void,
displaySearchResultsTooltip: @escaping (ASDisplayNode, CGRect) -> Void,
unarchivePeer: @escaping () -> Void,
scrollToTop: @escaping () -> Void,
viewReplies: @escaping (MessageId?, ChatReplyThreadMessage) -> Void,
activatePinnedListPreview: @escaping (ASDisplayNode, ContextGesture) -> Void,
joinGroupCall: @escaping (CachedChannelData.ActiveCall) -> Void,
presentInviteMembers: @escaping () -> Void,
presentGigagroupHelp: @escaping () -> Void,
openMonoforum: @escaping () -> Void,
editMessageMedia: @escaping (MessageId, Bool) -> Void,
updateShowCommands: @escaping ((Bool) -> Bool) -> Void,
updateShowSendAsPeers: @escaping ((Bool) -> Bool) -> Void,
openInviteRequests: @escaping () -> Void,
openSendAsPeer: @escaping (ASDisplayNode, ContextGesture?) -> Void,
presentChatRequestAdminInfo: @escaping () -> Void,
displayCopyProtectionTip: @escaping (UIView, Bool) -> Void,
openWebView: @escaping (String, String, Bool, ChatOpenWebViewSource) -> Void,
updateShowWebView: @escaping ((Bool) -> Bool) -> Void,
insertText: @escaping (NSAttributedString) -> Void,
backwardsDeleteText: @escaping () -> Void,
restartTopic: @escaping () -> Void,
toggleTranslation: @escaping (ChatTranslationDisplayType) -> Void,
changeTranslationLanguage: @escaping (String) -> Void,
addDoNotTranslateLanguage: @escaping (String) -> Void,
hideTranslationPanel: @escaping () -> Void,
openPremiumGift: @escaping () -> Void,
openSuggestPost: @escaping (Message?, OpenSuggestPostMode) -> Void,
openPremiumRequiredForMessaging: @escaping () -> Void,
openStarsPurchase: @escaping (Int64?) -> Void,
openMessagePayment: @escaping () -> Void,
openBoostToUnrestrict: @escaping () -> Void,
updateRecordingTrimRange: @escaping (Double, Double, Bool, Bool) -> Void,
dismissAllTooltips: @escaping () -> Void,
editTodoMessage: @escaping (MessageId, Int32?, Bool) -> Void,
dismissUrlPreview: @escaping () -> Void,
dismissForwardMessages: @escaping () -> Void,
dismissSuggestPost: @escaping () -> Void,
displayUndo: @escaping (UndoOverlayContent) -> Void,
sendEmoji: @escaping (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void,
updateHistoryFilter: @escaping ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void,
updateChatLocationThread: @escaping (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void,
toggleChatSidebarMode: @escaping () -> Void,
updateDisplayHistoryFilterAsList: @escaping (Bool) -> Void,
requestLayout: @escaping (ContainedViewLayoutTransition) -> Void,
chatController: @escaping () -> ViewController?,
statuses: ChatPanelInterfaceInteractionStatuses?
) {
self.setupReplyMessage = setupReplyMessage
self.setupEditMessage = setupEditMessage
self.beginMessageSelection = beginMessageSelection
self.cancelMessageSelection = cancelMessageSelection
self.deleteSelectedMessages = deleteSelectedMessages
self.reportSelectedMessages = reportSelectedMessages
self.reportMessages = reportMessages
self.blockMessageAuthor = blockMessageAuthor
self.deleteMessages = deleteMessages
self.forwardSelectedMessages = forwardSelectedMessages
self.forwardCurrentForwardMessages = forwardCurrentForwardMessages
self.forwardMessages = forwardMessages
self.updateForwardOptionsState = updateForwardOptionsState
self.presentForwardOptions = presentForwardOptions
self.presentReplyOptions = presentReplyOptions
self.presentLinkOptions = presentLinkOptions
self.presentSuggestPostOptions = presentSuggestPostOptions
self.shareSelectedMessages = shareSelectedMessages
self.updateTextInputStateAndMode = updateTextInputStateAndMode
self.updateInputModeAndDismissedButtonKeyboardMessageId = updateInputModeAndDismissedButtonKeyboardMessageId
self.openStickers = openStickers
self.editMessage = editMessage
self.beginMessageSearch = beginMessageSearch
self.dismissMessageSearch = dismissMessageSearch
self.updateMessageSearch = updateMessageSearch
self.openSearchResults = openSearchResults
self.navigateMessageSearch = navigateMessageSearch
self.openCalendarSearch = openCalendarSearch
self.toggleMembersSearch = toggleMembersSearch
self.navigateToMessage = navigateToMessage
self.navigateToChat = navigateToChat
self.navigateToProfile = navigateToProfile
self.openPeerInfo = openPeerInfo
self.togglePeerNotifications = togglePeerNotifications
self.sendContextResult = sendContextResult
self.sendBotCommand = sendBotCommand
self.sendShortcut = sendShortcut
self.openEditShortcuts = openEditShortcuts
self.sendBotStart = sendBotStart
self.botSwitchChatWithPayload = botSwitchChatWithPayload
self.beginMediaRecording = beginMediaRecording
self.finishMediaRecording = finishMediaRecording
self.stopMediaRecording = stopMediaRecording
self.lockMediaRecording = lockMediaRecording
self.resumeMediaRecording = resumeMediaRecording
self.deleteRecordedMedia = deleteRecordedMedia
self.sendRecordedMedia = sendRecordedMedia
self.displayRestrictedInfo = displayRestrictedInfo
self.displayVideoUnmuteTip = displayVideoUnmuteTip
self.switchMediaRecordingMode = switchMediaRecordingMode
self.setupMessageAutoremoveTimeout = setupMessageAutoremoveTimeout
self.sendSticker = sendSticker
self.unblockPeer = unblockPeer
self.pinMessage = pinMessage
self.unpinMessage = unpinMessage
self.unpinAllMessages = unpinAllMessages
self.openPinnedList = openPinnedList
self.shareAccountContact = shareAccountContact
self.reportPeer = reportPeer
self.presentPeerContact = presentPeerContact
self.dismissReportPeer = dismissReportPeer
self.deleteChat = deleteChat
self.beginCall = beginCall
self.toggleMessageStickerStarred = toggleMessageStickerStarred
self.presentController = presentController
self.presentControllerInCurrent = presentControllerInCurrent
self.getNavigationController = getNavigationController
self.presentGlobalOverlayController = presentGlobalOverlayController
self.navigateFeed = navigateFeed
self.openGrouping = openGrouping
self.toggleSilentPost = toggleSilentPost
self.requestUnvoteInMessage = requestUnvoteInMessage
self.requestStopPollInMessage = requestStopPollInMessage
self.updateInputLanguage = updateInputLanguage
self.unarchiveChat = unarchiveChat
self.openLinkEditing = openLinkEditing
self.displaySlowmodeTooltip = displaySlowmodeTooltip
self.displaySendMessageOptions = displaySendMessageOptions
self.openScheduledMessages = openScheduledMessages
self.openPeersNearby = openPeersNearby
self.displaySearchResultsTooltip = displaySearchResultsTooltip
self.unarchivePeer = unarchivePeer
self.scrollToTop = scrollToTop
self.viewReplies = viewReplies
self.activatePinnedListPreview = activatePinnedListPreview
self.editMessageMedia = editMessageMedia
self.joinGroupCall = joinGroupCall
self.presentInviteMembers = presentInviteMembers
self.presentGigagroupHelp = presentGigagroupHelp
self.openMonoforum = openMonoforum
self.updateShowCommands = updateShowCommands
self.updateShowSendAsPeers = updateShowSendAsPeers
self.openInviteRequests = openInviteRequests
self.openSendAsPeer = openSendAsPeer
self.presentChatRequestAdminInfo = presentChatRequestAdminInfo
self.displayCopyProtectionTip = displayCopyProtectionTip
self.openWebView = openWebView
self.updateShowWebView = updateShowWebView
self.insertText = insertText
self.backwardsDeleteText = backwardsDeleteText
self.restartTopic = restartTopic
self.toggleTranslation = toggleTranslation
self.changeTranslationLanguage = changeTranslationLanguage
self.addDoNotTranslateLanguage = addDoNotTranslateLanguage
self.hideTranslationPanel = hideTranslationPanel
self.openPremiumGift = openPremiumGift
self.openSuggestPost = openSuggestPost
self.openPremiumRequiredForMessaging = openPremiumRequiredForMessaging
self.openStarsPurchase = openStarsPurchase
self.openMessagePayment = openMessagePayment
self.openBoostToUnrestrict = openBoostToUnrestrict
self.updateRecordingTrimRange = updateRecordingTrimRange
self.dismissAllTooltips = dismissAllTooltips
self.editTodoMessage = editTodoMessage
self.dismissUrlPreview = dismissUrlPreview
self.dismissForwardMessages = dismissForwardMessages
self.dismissSuggestPost = dismissSuggestPost
self.displayUndo = displayUndo
self.sendEmoji = sendEmoji
self.updateHistoryFilter = updateHistoryFilter
self.updateChatLocationThread = updateChatLocationThread
self.toggleChatSidebarMode = toggleChatSidebarMode
self.updateDisplayHistoryFilterAsList = updateDisplayHistoryFilterAsList
self.requestLayout = requestLayout
self.chatController = chatController
self.statuses = statuses
}
public convenience init(
updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void,
updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void,
openLinkEditing: @escaping () -> Void
) {
self.init(setupReplyMessage: { _, _, _ in
}, setupEditMessage: { _, _ in
}, beginMessageSelection: { _, _ in
}, cancelMessageSelection: { _ in
}, deleteSelectedMessages: {
}, reportSelectedMessages: {
}, reportMessages: { _, _ in
}, blockMessageAuthor: { _, _ in
}, deleteMessages: { _, _, f in
f(.default)
}, forwardSelectedMessages: {
}, forwardCurrentForwardMessages: {
}, forwardMessages: { _ in
}, updateForwardOptionsState: { _ in
}, presentForwardOptions: { _ in
}, presentReplyOptions: { _ in
}, presentLinkOptions: { _ in
}, presentSuggestPostOptions: {
}, shareSelectedMessages: {
}, updateTextInputStateAndMode: updateTextInputStateAndMode, updateInputModeAndDismissedButtonKeyboardMessageId: updateInputModeAndDismissedButtonKeyboardMessageId, openStickers: {
}, editMessage: {
}, beginMessageSearch: { _, _ in
}, dismissMessageSearch: {
}, updateMessageSearch: { _ in
}, openSearchResults: {
}, navigateMessageSearch: { _ in
}, openCalendarSearch: {
}, toggleMembersSearch: { _ in
}, navigateToMessage: { _, _, _, _ in
}, navigateToChat: { _ in
}, navigateToProfile: { _ in
}, openPeerInfo: {
}, togglePeerNotifications: {
}, sendContextResult: { _, _, _, _ in
return false
}, sendBotCommand: { _, _ in
}, sendShortcut: { _ in
}, openEditShortcuts: {
}, sendBotStart: { _ in
}, botSwitchChatWithPayload: { _, _ in
}, beginMediaRecording: { _ in
}, finishMediaRecording: { _ in
}, stopMediaRecording: {
}, lockMediaRecording: {
}, resumeMediaRecording: {
}, deleteRecordedMedia: {
}, sendRecordedMedia: { _, _ in
}, displayRestrictedInfo: { _, _ in
}, displayVideoUnmuteTip: { _ in
}, switchMediaRecordingMode: {
}, setupMessageAutoremoveTimeout: {
}, sendSticker: { _, _, _, _, _, _ in
return false
}, unblockPeer: {
}, pinMessage: { _, _ in
}, unpinMessage: { _, _, _ in
}, unpinAllMessages: {
}, openPinnedList: { _ in
}, shareAccountContact: {
}, reportPeer: {
}, presentPeerContact: {
}, dismissReportPeer: {
}, deleteChat: {
}, beginCall: { _ in
}, toggleMessageStickerStarred: { _ in
}, presentController: { _, _ in
}, presentControllerInCurrent: { _, _ in
}, getNavigationController: {
return nil
}, presentGlobalOverlayController: { _, _ in
}, navigateFeed: {
}, openGrouping: {
}, toggleSilentPost: {
}, requestUnvoteInMessage: { _ in
}, requestStopPollInMessage: { _ in
}, updateInputLanguage: { _ in
}, unarchiveChat: {
}, openLinkEditing: openLinkEditing,
displaySlowmodeTooltip: { _, _ in
}, displaySendMessageOptions: { _, _ in
}, openScheduledMessages: {
}, openPeersNearby: {
}, displaySearchResultsTooltip: { _, _ in
}, unarchivePeer: {
}, scrollToTop: {
}, viewReplies: { _, _ in
}, activatePinnedListPreview: { _, _ in
}, joinGroupCall: { _ in
}, presentInviteMembers: {
}, presentGigagroupHelp: {
}, openMonoforum: {
}, editMessageMedia: { _, _ in
}, updateShowCommands: { _ in
}, updateShowSendAsPeers: { _ in
}, openInviteRequests: {
}, openSendAsPeer: { _, _ in
}, presentChatRequestAdminInfo: {
}, displayCopyProtectionTip: { _, _ in
}, openWebView: { _, _, _, _ in
}, updateShowWebView: { _ in
}, insertText: { _ in
}, backwardsDeleteText: {
}, restartTopic: {
}, toggleTranslation: { _ in
}, changeTranslationLanguage: { _ in
}, addDoNotTranslateLanguage: { _ in
}, hideTranslationPanel: {
}, openPremiumGift: {
}, openSuggestPost: { _, _ in
}, openPremiumRequiredForMessaging: {
}, openStarsPurchase: { _ in
}, openMessagePayment: {
}, openBoostToUnrestrict: {
}, updateRecordingTrimRange: { _, _, _, _ in
}, dismissAllTooltips: {
}, editTodoMessage: { _, _, _ in
}, dismissUrlPreview: {
}, dismissForwardMessages: {
}, dismissSuggestPost: {
}, displayUndo: { _ in
}, sendEmoji: { _, _, _ in
}, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: {
}, updateDisplayHistoryFilterAsList: { _ in
}, requestLayout: { _ in
}, chatController: {
return nil
}, statuses: nil)
}
}
@@ -0,0 +1,226 @@
import Foundation
import TextFormat
import TelegramCore
import AccountContext
public func chatTextInputAddFormattingAttribute(_ state: ChatTextInputState, attribute: NSAttributedString.Key, value: Any?) -> ChatTextInputState {
if !state.selectionRange.isEmpty {
let nsRange = NSRange(location: state.selectionRange.lowerBound, length: state.selectionRange.count)
var addAttribute = true
var attributesToRemove: [NSAttributedString.Key] = []
state.inputText.enumerateAttributes(in: nsRange, options: .longestEffectiveRangeNotRequired) { attributes, range, _ in
for (key, _) in attributes {
if key == attribute {
if nsRange == range {
addAttribute = false
attributesToRemove.append(key)
}
}
}
}
var selectionRange = state.selectionRange
let result = NSMutableAttributedString(attributedString: state.inputText)
for attribute in attributesToRemove {
if attribute == ChatTextInputAttributes.block {
var removeRange = nsRange
var selectionIndex = nsRange.upperBound
if nsRange.upperBound != result.length && (result.string as NSString).character(at: nsRange.upperBound) != 0x0a {
result.insert(NSAttributedString(string: "\n"), at: nsRange.upperBound)
selectionIndex += 1
removeRange.length += 1
}
if nsRange.lowerBound != 0 && (result.string as NSString).character(at: nsRange.lowerBound - 1) != 0x0a {
result.insert(NSAttributedString(string: "\n"), at: nsRange.lowerBound)
selectionIndex += 1
removeRange.location += 1
} else if nsRange.lowerBound != 0 {
removeRange.location -= 1
removeRange.length += 1
}
if removeRange.lowerBound > result.length {
removeRange = NSRange(location: result.length, length: 0)
} else if removeRange.upperBound > result.length {
removeRange = NSRange(location: removeRange.lowerBound, length: result.length - removeRange.lowerBound)
}
result.removeAttribute(attribute, range: removeRange)
if selectionRange.lowerBound > result.length {
selectionRange = result.length ..< result.length
} else if selectionRange.upperBound > result.length {
selectionRange = selectionRange.lowerBound ..< result.length
}
// Prevent merge back
result.enumerateAttributes(in: NSRange(location: selectionIndex, length: result.length - selectionIndex), options: .longestEffectiveRangeNotRequired) { attributes, range, _ in
for (key, value) in attributes {
if let value = value as? ChatTextInputTextQuoteAttribute {
result.removeAttribute(key, range: range)
result.addAttribute(key, value: ChatTextInputTextQuoteAttribute(kind: value.kind, isCollapsed: value.isCollapsed), range: range)
}
}
}
selectionRange = selectionIndex ..< selectionIndex
} else {
result.removeAttribute(attribute, range: nsRange)
}
}
if addAttribute {
if attribute == ChatTextInputAttributes.block {
result.addAttribute(attribute, value: value ?? ChatTextInputTextQuoteAttribute(kind: .quote, isCollapsed: false), range: nsRange)
var selectionIndex = nsRange.upperBound
if nsRange.upperBound != result.length && (result.string as NSString).character(at: nsRange.upperBound) != 0x0a {
result.insert(NSAttributedString(string: "\n"), at: nsRange.upperBound)
selectionIndex += 1
}
if nsRange.lowerBound != 0 && (result.string as NSString).character(at: nsRange.lowerBound - 1) != 0x0a {
result.insert(NSAttributedString(string: "\n"), at: nsRange.lowerBound)
selectionIndex += 1
}
selectionRange = selectionIndex ..< selectionIndex
} else {
result.addAttribute(attribute, value: true as Bool, range: nsRange)
}
}
if selectionRange.lowerBound > result.length {
selectionRange = result.length ..< result.length
} else if selectionRange.upperBound > result.length {
selectionRange = selectionRange.lowerBound ..< result.length
}
return ChatTextInputState(inputText: result, selectionRange: selectionRange)
} else {
return state
}
}
public func chatTextInputClearFormattingAttributes(_ state: ChatTextInputState) -> ChatTextInputState {
if !state.selectionRange.isEmpty {
let nsRange = NSRange(location: state.selectionRange.lowerBound, length: state.selectionRange.count)
var attributesToRemove: [NSAttributedString.Key] = []
state.inputText.enumerateAttributes(in: nsRange, options: .longestEffectiveRangeNotRequired) { attributes, range, stop in
for (key, _) in attributes {
attributesToRemove.append(key)
}
}
let result = NSMutableAttributedString(attributedString: state.inputText)
for attribute in attributesToRemove {
result.removeAttribute(attribute, range: nsRange)
}
return ChatTextInputState(inputText: result, selectionRange: state.selectionRange)
} else {
return state
}
}
public func chatTextInputAddLinkAttribute(_ state: ChatTextInputState, selectionRange: Range<Int>, url: String) -> ChatTextInputState {
if !selectionRange.isEmpty {
let nsRange = NSRange(location: selectionRange.lowerBound, length: selectionRange.count)
var linkRange = nsRange
var attributesToRemove: [(NSAttributedString.Key, NSRange)] = []
state.inputText.enumerateAttributes(in: nsRange, options: .longestEffectiveRangeNotRequired) { attributes, range, stop in
for (key, _) in attributes {
if key == ChatTextInputAttributes.textUrl {
attributesToRemove.append((key, range))
linkRange = linkRange.union(range)
} else {
attributesToRemove.append((key, nsRange))
}
}
}
let result = NSMutableAttributedString(attributedString: state.inputText)
for (attribute, range) in attributesToRemove {
result.removeAttribute(attribute, range: range)
}
result.addAttribute(ChatTextInputAttributes.textUrl, value: ChatTextInputTextUrlAttribute(url: url), range: nsRange)
return ChatTextInputState(inputText: result, selectionRange: selectionRange)
} else {
return state
}
}
public func chatTextInputRemoveLinkAttribute(_ state: ChatTextInputState, selectionRange: Range<Int>) -> ChatTextInputState {
if !selectionRange.isEmpty {
let nsRange = NSRange(location: selectionRange.lowerBound, length: selectionRange.count)
var attributesToRemove: [(NSAttributedString.Key, NSRange)] = []
state.inputText.enumerateAttributes(in: nsRange, options: .longestEffectiveRangeNotRequired) { attributes, range, stop in
for (key, _) in attributes {
if key == ChatTextInputAttributes.textUrl {
attributesToRemove.append((key, range))
} else {
attributesToRemove.append((key, nsRange))
}
}
}
let result = NSMutableAttributedString(attributedString: state.inputText)
for (attribute, range) in attributesToRemove {
result.removeAttribute(attribute, range: range)
}
return ChatTextInputState(inputText: result, selectionRange: selectionRange)
} else {
return state
}
}
public func chatTextInputAddMentionAttribute(_ state: ChatTextInputState, peer: EnginePeer) -> ChatTextInputState {
let inputText = NSMutableAttributedString(attributedString: state.inputText)
let range = NSMakeRange(state.selectionRange.startIndex, state.selectionRange.endIndex - state.selectionRange.startIndex)
if let addressName = peer.addressName, !addressName.isEmpty {
let replacementText = "@\(addressName) "
inputText.replaceCharacters(in: range, with: replacementText)
let selectionPosition = range.lowerBound + (replacementText as NSString).length
return ChatTextInputState(inputText: inputText, selectionRange: selectionPosition ..< selectionPosition)
} else if !peer.compactDisplayTitle.isEmpty {
let replacementText = NSMutableAttributedString()
replacementText.append(NSAttributedString(string: peer.compactDisplayTitle, attributes: [ChatTextInputAttributes.textMention: ChatTextInputTextMentionAttribute(peerId: peer.id)]))
replacementText.append(NSAttributedString(string: " "))
let updatedRange = NSRange(location: range.location , length: range.length)
inputText.replaceCharacters(in: updatedRange, with: replacementText)
let selectionPosition = updatedRange.lowerBound + replacementText.length
return ChatTextInputState(inputText: inputText, selectionRange: selectionPosition ..< selectionPosition)
} else {
return state
}
}
public func chatTextInputAddQuoteAttribute(_ state: ChatTextInputState, selectionRange: Range<Int>, kind: ChatTextInputTextQuoteAttribute.Kind) -> ChatTextInputState {
if selectionRange.isEmpty {
return state
}
let nsRange = NSRange(location: selectionRange.lowerBound, length: selectionRange.count)
var quoteRange = nsRange
var attributesToRemove: [(NSAttributedString.Key, NSRange)] = []
state.inputText.enumerateAttributes(in: nsRange, options: .longestEffectiveRangeNotRequired) { attributes, range, stop in
for (key, _) in attributes {
if key == ChatTextInputAttributes.block {
attributesToRemove.append((key, range))
quoteRange = quoteRange.union(range)
} else {
attributesToRemove.append((key, nsRange))
}
}
}
let result = NSMutableAttributedString(attributedString: state.inputText)
for (attribute, range) in attributesToRemove {
result.removeAttribute(attribute, range: range)
}
result.addAttribute(ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: kind, isCollapsed: false), range: nsRange)
return ChatTextInputState(inputText: result, selectionRange: selectionRange)
}
@@ -0,0 +1,173 @@
import Foundation
import AccountContext
import SwiftSignalKit
public enum ChatTextInputAccessoryItem: Equatable {
public enum Key: Hashable {
case input
case botInput
case commands
case silentPost
case messageAutoremoveTimeout
case scheduledMessages
case gift
case suggestPost
}
public enum InputMode: Hashable {
case keyboard
case stickers
case emoji
case bot
}
case input(isEnabled: Bool, inputMode: InputMode)
case botInput(isEnabled: Bool, inputMode: InputMode)
case commands
case silentPost(Bool)
case messageAutoremoveTimeout(Int32?)
case scheduledMessages
case gift
case suggestPost
public var key: Key {
switch self {
case .input:
return .input
case .botInput:
return .botInput
case .commands:
return .commands
case .silentPost:
return .silentPost
case .messageAutoremoveTimeout:
return .messageAutoremoveTimeout
case .scheduledMessages:
return .scheduledMessages
case .gift:
return .gift
case .suggestPost:
return .suggestPost
}
}
}
public final class InstantVideoControllerRecordingStatus {
public let micLevel: Signal<Float, NoError>
public let duration: Signal<TimeInterval, NoError>
public init(micLevel: Signal<Float, NoError>, duration: Signal<TimeInterval, NoError>) {
self.micLevel = micLevel
self.duration = duration
}
}
public struct ChatTextInputPanelState: Equatable {
public let accessoryItems: [ChatTextInputAccessoryItem]
public let contextPlaceholder: NSAttributedString?
public let mediaRecordingState: ChatTextInputPanelMediaRecordingState?
public init(accessoryItems: [ChatTextInputAccessoryItem], contextPlaceholder: NSAttributedString?, mediaRecordingState: ChatTextInputPanelMediaRecordingState?) {
self.accessoryItems = accessoryItems
self.contextPlaceholder = contextPlaceholder
self.mediaRecordingState = mediaRecordingState
}
public init() {
self.accessoryItems = []
self.contextPlaceholder = nil
self.mediaRecordingState = nil
}
public func withUpdatedMediaRecordingState(_ mediaRecordingState: ChatTextInputPanelMediaRecordingState?) -> ChatTextInputPanelState {
return ChatTextInputPanelState(accessoryItems: self.accessoryItems, contextPlaceholder: self.contextPlaceholder, mediaRecordingState: mediaRecordingState)
}
public static func ==(lhs: ChatTextInputPanelState, rhs: ChatTextInputPanelState) -> Bool {
if lhs.accessoryItems != rhs.accessoryItems {
return false
}
if let lhsContextPlaceholder = lhs.contextPlaceholder, let rhsContextPlaceholder = rhs.contextPlaceholder {
return lhsContextPlaceholder.isEqual(to: rhsContextPlaceholder)
} else if (lhs.contextPlaceholder != nil) != (rhs.contextPlaceholder != nil) {
return false
}
if lhs.mediaRecordingState != rhs.mediaRecordingState {
return false
}
return true
}
}
public enum ChatVideoRecordingStatus: Equatable {
case recording(InstantVideoControllerRecordingStatus)
case editing
public static func ==(lhs: ChatVideoRecordingStatus, rhs: ChatVideoRecordingStatus) -> Bool {
switch lhs {
case let .recording(lhsStatus):
if case let .recording(rhsStatus) = rhs, lhsStatus === rhsStatus {
return true
} else {
return false
}
case .editing:
if case .editing = rhs {
return true
} else {
return false
}
}
}
}
public enum ChatTextInputPanelMediaRecordingState: Equatable {
case audio(recorder: ManagedAudioRecorder, isLocked: Bool)
case video(status: ChatVideoRecordingStatus, isLocked: Bool)
case waitingForPreview
public var isLocked: Bool {
switch self {
case let .audio(_, isLocked):
return isLocked
case let .video(_, isLocked):
return isLocked
case .waitingForPreview:
return true
}
}
public func withLocked(_ isLocked: Bool) -> ChatTextInputPanelMediaRecordingState {
switch self {
case let .audio(recorder, _):
return .audio(recorder: recorder, isLocked: isLocked)
case let .video(status, _):
return .video(status: status, isLocked: isLocked)
case .waitingForPreview:
return .waitingForPreview
}
}
public static func ==(lhs: ChatTextInputPanelMediaRecordingState, rhs: ChatTextInputPanelMediaRecordingState) -> Bool {
switch lhs {
case let .audio(lhsRecorder, lhsIsLocked):
if case let .audio(rhsRecorder, rhsIsLocked) = rhs, lhsRecorder === rhsRecorder, lhsIsLocked == rhsIsLocked {
return true
} else {
return false
}
case let .video(status, isLocked):
if case .video(status, isLocked) = rhs {
return true
} else {
return false
}
case .waitingForPreview:
if case .waitingForPreview = rhs {
return true
}
return false
}
}
}