feat: новые функции, исправлены критические ошибки сборки и баги интерфейса, больше подписей в файлах

This commit is contained in:
ichmagmaus 812
2026-03-04 22:06:16 +01:00
parent a614259289
commit f033954db2
81 changed files with 1256 additions and 298 deletions
@@ -692,6 +692,44 @@ extension ChatControllerImpl {
self.displayNode = ChatControllerNode(context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, subject: self.subject, controllerInteraction: self.controllerInteraction!, chatPresentationInterfaceState: self.presentationInterfaceState, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, navigationBar: self.navigationBar, statusBar: self.statusBar, backgroundNode: self.chatBackgroundNode, controller: self)
// Seed the title pill synchronously from whatever data is available at this point,
// so the header shows the peer name immediately instead of staying blank until
// the first async contentDataUpdated() fires.
let initialTitleContent: ChatTitleContent? = self.contentData?.state.chatTitleContent
?? self.presentationInterfaceState.renderedPeer.flatMap { renderedPeer -> ChatTitleContent? in
guard let peer = renderedPeer.peer else { return nil }
let peerData = ChatTitleContent.PeerData(
peerId: renderedPeer.peerId,
peer: peer,
isContact: false,
isSavedMessages: peer.id == self.context.account.peerId,
notificationSettings: nil,
peerPresences: [:],
cachedData: nil
)
return .peer(
peerView: peerData,
customTitle: nil,
customSubtitle: nil,
onlineMemberCount: (nil, nil),
isScheduledMessages: false,
isMuted: nil,
customMessageCount: nil,
isEnabled: true
)
}
if let initialTitleContent {
self.chatTitleView?.update(
context: self.context,
theme: self.presentationData.theme,
strings: self.presentationData.strings,
dateTimeFormat: self.presentationData.dateTimeFormat,
nameDisplayOrder: self.presentationData.nameDisplayOrder,
content: initialTitleContent,
transition: .immediate
)
}
if let currentItem = self.globalControlPanelsContext?.tempVoicePlaylistCurrentItem {
self.chatDisplayNode.historyNode.voicePlaylistItemChanged(nil, currentItem)
}
@@ -146,7 +146,6 @@ import ChatTextInputPanelNode
import ChatInputAccessoryPanel
import GlobalControlPanelsContext
import ChatSearchNavigationContentNode
import ChatAgeRestrictionAlertController
public final class ChatControllerOverlayPresentationData {
public let expandData: (ASDisplayNode?, () -> Void)
@@ -830,7 +829,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return true
}
let controllerInteraction = ChatControllerInteraction(openMessage: { [weak self] message, params in
let openMessage: (Message, OpenMessageParams) -> Bool = { [weak self] message, params in
guard let self, self.isNodeLoaded, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) else {
return false
}
@@ -1566,7 +1565,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.controllerInteraction?.isOpeningMediaSignal = openChatMessageParams.blockInteraction.get()
return context.sharedContext.openChatMessage(openChatMessageParams)
}, openPeer: { [weak self] peer, navigation, fromMessage, source in
}
let openPeer: (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, ChatControllerInteraction.OpenPeerSource) -> Void = { [weak self] peer, navigation, fromMessage, source in
var expandAvatar = false
if case let .groupParticipant(storyStats, avatarHeaderNode) = source {
if let storyStats, storyStats.totalCount != 0, let avatarHeaderNode = avatarHeaderNode as? ChatMessageAvatarHeaderNodeImpl {
@@ -1581,20 +1582,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
fromReactionMessageId = fromMessage?.id
}
self?.openPeer(peer: peer, navigation: navigation, fromMessage: fromMessage, fromReactionMessageId: fromReactionMessageId, expandAvatar: expandAvatar)
}, openPeerMention: { [weak self] name, progress in
}
let openPeerMention: (String, Promise<Bool>?) -> Void = { [weak self] name, progress in
self?.openPeerMention(name, progress: progress)
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame, anyRecognizer, location in
}
let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void = { [weak self] message, selectAll, node, frame, anyRecognizer, location in
guard let self, self.isNodeLoaded else {
return
}
self.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame, anyRecognizer: anyRecognizer, location: location)
}, openMessageReactionContextMenu: { [weak self] message, sourceView, gesture, value in
}
let openMessageReactionContextMenu: (Message, ContextExtractedContentContainingView, ContextGesture?, MessageReaction.Reaction) -> Void = { [weak self] message, sourceView, gesture, value in
guard let self else {
return
}
self.openMessageReactionContextMenu(message: message, sourceView: sourceView, gesture: gesture, value: value)
}, updateMessageReaction: { [weak self] initialMessage, reaction, force, sourceView in
}
let updateMessageReaction: (Message, ChatControllerInteractionReaction, Bool, ContextExtractedContentContainingView?) -> Void = { [weak self] initialMessage, reaction, force, sourceView in
guard let strongSelf = self else {
return
}
@@ -2062,7 +2071,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
})
}, activateMessagePinch: { [weak self] sourceNode in
}
let controllerInteraction = ChatControllerInteraction(
openMessage: openMessage,
openPeer: openPeer,
openPeerMention: openPeerMention,
openMessageContextMenu: openMessageContextMenu,
openMessageReactionContextMenu: openMessageReactionContextMenu,
updateMessageReaction: updateMessageReaction,
activateMessagePinch: { [weak self] sourceNode in
guard let strongSelf = self else {
return
}
@@ -893,7 +893,8 @@ extension ChatControllerImpl {
peerVerification = cachedChannelData.verification
}
}
copyProtectionEnabled = peer.isCopyProtectionEnabled
// GHOSTGRAM: Bypass copy protection if enabled in Misc settings
copyProtectionEnabled = MiscSettingsManager.shared.shouldBypassCopyProtection ? false : peer.isCopyProtectionEnabled
if let cachedGroupData = peerView.cachedData as? CachedGroupData {
if !cachedGroupData.botInfos.isEmpty {
hasBots = true
@@ -1371,7 +1372,8 @@ extension ChatControllerImpl {
var alwaysShowGiftButton = false
var disallowedGifts: TelegramDisallowedGifts?
if let peer = peerView.peers[peerView.peerId] {
copyProtectionEnabled = peer.isCopyProtectionEnabled
// GHOSTGRAM: Bypass copy protection if enabled in Misc settings
copyProtectionEnabled = MiscSettingsManager.shared.shouldBypassCopyProtection ? false : peer.isCopyProtectionEnabled
if let cachedData = peerView.cachedData as? CachedUserData {
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot)
if case let .known(value) = cachedData.businessIntro {
@@ -1129,7 +1129,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
let isSecret = self.chatPresentationInterfaceState.copyProtectionEnabled || self.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat || self.chatLocation.peerId?.isVerificationCodes == true
// GHOSTGRAM: Bypass screenshot protection if enabled in Misc settings
let effectiveCopyProtection = MiscSettingsManager.shared.shouldBypassScreenshotProtection ? false : self.chatPresentationInterfaceState.copyProtectionEnabled
let isSecret = effectiveCopyProtection || self.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat || self.chatLocation.peerId?.isVerificationCodes == true
if self.historyNodeContainer.isSecret != isSecret {
self.historyNodeContainer.isSecret = isSecret
setLayerDisableScreenshots(self.titleAccessoryPanelContainer.layer, isSecret)
@@ -4600,12 +4602,10 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
self.setupSendActionOnViewUpdate({ [weak self] in
guard let self, let textInputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode else {
return
}
// GHOSTGRAM: When send delay is active, scheduled messages
// don't trigger history view update, so clear input immediately.
if SendDelayManager.shared.isEnabled {
self.collapseInput()
self.ignoreUpdateHeight = true
textInputPanelNode.text = ""
self.requestUpdateChatInterfaceState(.immediate, overrideThreadId == nil, { state in
@@ -4624,7 +4624,33 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
return state
})
self.ignoreUpdateHeight = false
}, usedCorrelationId)
} else {
self.setupSendActionOnViewUpdate({ [weak self] in
guard let self, let textInputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode else {
return
}
self.collapseInput()
self.ignoreUpdateHeight = true
textInputPanelNode.text = ""
self.requestUpdateChatInterfaceState(.immediate, overrideThreadId == nil, { state in
var state = state
state = state.withUpdatedReplyMessageSubject(nil)
state = state.withUpdatedSendMessageEffect(nil)
if state.postSuggestionState != nil {
state = state.withUpdatedPostSuggestionState(nil)
state = state.withUpdatedEditMessage(nil)
}
state = state.withUpdatedForwardMessageIds(nil)
state = state.withUpdatedForwardOptionsState(nil)
state = state.withUpdatedComposeDisableUrlPreviews([])
return state
})
self.ignoreUpdateHeight = false
}, usedCorrelationId)
}
completion()
self.sendMessages(messages, silentPosting, scheduleTime, repeatPeriod, messages.count > 1, postpone)
@@ -2056,6 +2056,10 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
}
}
}
// GHOSTGRAM: Bypass copy protection if enabled in Misc settings
if MiscSettingsManager.shared.shouldBypassCopyProtection {
isCopyProtectionEnabled = false
}
let alwaysDisplayTranscribeButton = ChatMessageItemAssociatedData.DisplayTranscribeButton(
canBeDisplayed: suggestAudioTranscription.0 < 2,
displayForNotConsumed: suggestAudioTranscription.1,
@@ -2102,7 +2106,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
selectedMessages: selectedMessages,
presentationData: chatPresentationData,
historyAppearsCleared: historyAppearsCleared,
skipViewOnceMedia: mode != .bubbles,
// GHOSTGRAM: Keep view-once media visible if bypass is enabled
skipViewOnceMedia: MiscSettingsManager.shared.shouldDisableViewOnceAutoDelete ? false : (mode != .bubbles),
pendingUnpinnedAllMessages: pendingUnpinnedAllMessages,
pendingRemovedMessages: pendingRemovedMessages,
associatedData: associatedData,
@@ -2110,8 +2115,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
customChannelDiscussionReadState: customChannelDiscussionReadState,
customThreadOutgoingReadState: customThreadOutgoingReadState,
cachedData: data.cachedData,
adMessage: allAdMessages.fixed,
dynamicAdMessages: allAdMessages.opportunistic
// GHOSTGRAM: Block ads if enabled in Misc settings
adMessage: MiscSettingsManager.shared.shouldBlockAds ? nil : allAdMessages.fixed,
dynamicAdMessages: MiscSettingsManager.shared.shouldBlockAds ? [] : allAdMessages.opportunistic
)
let lastHeaderId = filteredEntries.last.flatMap { listMessageDateHeaderId(timestamp: $0.index.timestamp) } ?? 0
let processedView = ChatHistoryView(originalView: view, filteredEntries: filteredEntries, associatedData: associatedData, lastHeaderId: lastHeaderId, id: id, locationInput: update.2, ignoreMessagesInTimestampRange: update.3, ignoreMessageIds: update.4)
@@ -31,7 +31,7 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode {
self.chatLocation = chatLocation
self.interaction = interaction
self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasBackground: false, hasSeparator: false), strings: strings, fieldStyle: .modern)
self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasBackground: false, hasSeparator: false), presentationTheme: theme, strings: strings, fieldStyle: .modern)
let placeholderText: String
switch chatLocation {
case .peer, .replyThread, .customChatContents:
@@ -90,10 +90,11 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode {
return 54.0
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 54.0))
self.searchBar.frame = searchBarFrame
self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition)
return size
}
func activate() {
@@ -106,7 +107,7 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode {
func update(presentationInterfaceState: ChatPresentationInterfaceState) {
if let search = presentationInterfaceState.search {
self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationInterfaceState.theme, hasBackground: false, hasSeparator: false), strings: presentationInterfaceState.strings)
self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationInterfaceState.theme, hasBackground: false, hasSeparator: false), presentationTheme: presentationInterfaceState.theme, strings: presentationInterfaceState.strings)
switch search.domain {
case .everything, .tag:
@@ -1,16 +1,4 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ChatPresentationInterfaceState
import AccountContext
import class LegacyChatHeaderPanelComponent.LegacyChatTitleAccessoryPanelNode
class ChatTitleAccessoryPanelNode: ASDisplayNode {
typealias LayoutResult = ChatControllerCustomNavigationPanelNode.LayoutResult
var interfaceInteraction: ChatPanelInterfaceInteraction?
func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult {
preconditionFailure()
}
class ChatTitleAccessoryPanelNode: LegacyChatTitleAccessoryPanelNode {
}