This commit is contained in:
Isaac 2026-03-24 01:39:57 +08:00
commit 9c0e6f6151
9 changed files with 88 additions and 51 deletions

View file

@ -2686,6 +2686,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
return false
}
}
if let _ = message.media.first(where: { $0 is TelegramMediaPoll }) {
entities = []
}
if message.id.peerId.isTelegramNotifications || message.id.peerId.isVerificationCodes {
let regex: NSRegularExpression?

View file

@ -396,11 +396,9 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
}
case let poll as TelegramMediaPoll:
messageText = poll.text
customEmojiRanges = []
for entity in poll.textEntities {
if case let .CustomEmoji(_, fileId) = entity.type {
if customEmojiRanges == nil {
customEmojiRanges = []
}
let range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)
let attribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: message.associatedMedia[EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile)
customEmojiRanges?.append((range, attribute))

View file

@ -148,6 +148,8 @@ func presentLegacyMediaPickerGallery(
paintStickersContext.livePhotoButton = {
if case .secretChat = peer {
return nil
} else if peer == nil {
return nil
}
let livePhotoButton = LivePhotoButton()
livePhotoButton.present = present

View file

@ -1343,6 +1343,10 @@ public final class TelegramMediaFile: Media, Equatable, Codable {
public func withUpdatedVideoCover(_ videoCover: TelegramMediaImage?) -> TelegramMediaFile {
return TelegramMediaFile(fileId: self.fileId, partialReference: self.partialReference, resource: self.resource, previewRepresentations: self.previewRepresentations, videoThumbnails: self.videoThumbnails, videoCover: videoCover, immediateThumbnailData: self.immediateThumbnailData, mimeType: self.mimeType, size: self.size, attributes: self.attributes, alternativeRepresentations: self.alternativeRepresentations)
}
public func withUpdatedImmediateThumnailData(_ immediateThumbnailData: Data?) -> TelegramMediaFile {
return TelegramMediaFile(fileId: self.fileId, partialReference: self.partialReference, resource: self.resource, previewRepresentations: self.previewRepresentations, videoThumbnails: self.videoThumbnails, videoCover: self.videoCover, immediateThumbnailData: immediateThumbnailData, mimeType: self.mimeType, size: self.size, attributes: self.attributes, alternativeRepresentations: self.alternativeRepresentations)
}
}
public func ==(lhs: TelegramMediaFile, rhs: TelegramMediaFile) -> Bool {

View file

@ -919,6 +919,8 @@ public func makeAttachmentFileControllerImpl(
arguments.send(message)
}, dismissInput: {
dismissInputImpl?()
}, didPreviewAudio: {
didPreviewAudio = true
})
}

View file

@ -32,12 +32,13 @@ final class AttachmentFileSearchItem: ItemListControllerSearch {
let cancel: () -> Void
let send: (Message) -> Void
let dismissInput: () -> Void
let didPreviewAudio: () -> Void
private var updateActivity: ((Bool) -> Void)?
private var activity: ValuePromise<Bool> = ValuePromise(ignoreRepeated: false)
private let activityDisposable = MetaDisposable()
init(context: AccountContext, mode: AttachmentFileControllerMode, presentationData: PresentationData, focus: @escaping () -> Void, cancel: @escaping () -> Void, send: @escaping (Message) -> Void, dismissInput: @escaping () -> Void) {
init(context: AccountContext, mode: AttachmentFileControllerMode, presentationData: PresentationData, focus: @escaping () -> Void, cancel: @escaping () -> Void, send: @escaping (Message) -> Void, dismissInput: @escaping () -> Void, didPreviewAudio: @escaping () -> Void) {
self.context = context
self.mode = mode
self.presentationData = presentationData
@ -45,6 +46,7 @@ final class AttachmentFileSearchItem: ItemListControllerSearch {
self.cancel = cancel
self.send = send
self.dismissInput = dismissInput
self.didPreviewAudio = didPreviewAudio
self.activityDisposable.set((activity.get() |> mapToSignal { value -> Signal<Bool, NoError> in
if value {
return .single(value) |> delay(0.2, queue: Queue.mainQueue())
@ -89,7 +91,7 @@ final class AttachmentFileSearchItem: ItemListControllerSearch {
func node(current: ItemListControllerSearchNode?, titleContentNode: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> ItemListControllerSearchNode {
return AttachmentFileSearchItemNode(context: self.context, mode: self.mode, presentationData: self.presentationData, focus: self.focus, send: self.send, cancel: self.cancel, updateActivity: { [weak self] value in
self?.activity.set(value)
}, dismissInput: self.dismissInput)
}, dismissInput: self.dismissInput, didPreviewAudio: self.didPreviewAudio)
}
}
@ -106,7 +108,7 @@ private final class AttachmentFileSearchItemNode: ItemListControllerSearchNode {
private var validLayout: ContainerViewLayout?
init(context: AccountContext, mode: AttachmentFileControllerMode, presentationData: PresentationData, focus: @escaping () -> Void, send: @escaping (Message) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, dismissInput: @escaping () -> Void) {
init(context: AccountContext, mode: AttachmentFileControllerMode, presentationData: PresentationData, focus: @escaping () -> Void, send: @escaping (Message) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, dismissInput: @escaping () -> Void, didPreviewAudio: @escaping () -> Void) {
self.context = context
self.mode = mode
self.presentationData = presentationData
@ -115,7 +117,7 @@ private final class AttachmentFileSearchItemNode: ItemListControllerSearchNode {
self.containerNode = AttachmentFileSearchContainerNode(context: context, mode: mode, presentationData: presentationData, send: { message in
send(message)
}, updateActivity: updateActivity)
}, updateActivity: updateActivity, didPreviewAudio: didPreviewAudio)
super.init()
@ -413,7 +415,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
return _hasDim
}
public init(context: AccountContext, mode: AttachmentFileControllerMode, presentationData: PresentationData, send: @escaping (Message) -> Void, updateActivity: @escaping (Bool) -> Void) {
public init(context: AccountContext, mode: AttachmentFileControllerMode, presentationData: PresentationData, send: @escaping (Message) -> Void, updateActivity: @escaping (Bool) -> Void, didPreviewAudio: @escaping () -> Void) {
self.context = context
self.send = send
@ -476,7 +478,6 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
self.addSubnode(self.emptyResultsTitleNode)
self.addSubnode(self.emptyResultsTextNode)
let interaction = AttachmentFileSearchContainerInteraction(
context: context,
send: { [weak self] message in
@ -484,6 +485,8 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
self?.listNode.clearHighlightAnimated(true)
},
toggleMediaPlayback: { message in
didPreviewAudio()
let playlistLocation: PeerMessagesPlaylistLocation = .custom(messages: .single(([message], 0, false)), canReorder: false, at: message.id, loadMore: nil, hidePanel: true)
context.sharedContext.mediaManager.setPlaylist((context, PeerMessagesMediaPlaylist(context: context, location: playlistLocation, chatLocationContextHolder: nil)), type: .music, control: .playback(.togglePlayPause))
},

View file

@ -1511,7 +1511,12 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
}
})
effectiveFile = image.video
if var video = image.video {
if video.immediateThumbnailData == nil {
video = video.withUpdatedImmediateThumnailData(image.immediateThumbnailData)
}
effectiveFile = video
}
} else if let image = media as? TelegramMediaWebFile {
if hasCurrentVideoNode {
replaceVideoNode = true

View file

@ -835,7 +835,26 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
// }
let rightInset: CGFloat = 10.0 + mediaInset
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: optionAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: max(1.0, constrainedWidth - leftInset - rightInset), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0)))
let recentVoterPeers: [Peer]
if let optionResult {
recentVoterPeers = optionResult.recentVoterPeerIds.compactMap { message.peers[$0] }
} else {
recentVoterPeers = []
}
var titleTrailingInset = rightInset
if let countImage = updatedCountImage ?? maybeNode?.countImage, displayCount {
titleTrailingInset += countImage.size.width + ChatMessagePollOptionNode.countSpacing
}
if !recentVoterPeers.isEmpty {
var avatarsReservedWidth = ChatMessagePollOptionNode.avatarsSize.width - 15.0 + ChatMessagePollOptionNode.avatarsSpacing
if recentVoterPeers.count > 1 {
avatarsReservedWidth += 15.0
}
titleTrailingInset += avatarsReservedWidth
}
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: optionAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: max(1.0, constrainedWidth - leftInset - titleTrailingInset), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0)))
let contentLayoutHeight: CGFloat = max(52.0, titleLayout.size.height + 28.0)
let contentHeight: CGFloat = contentLayoutHeight + (optionResult != nil ? 7.0 : 0.0)
@ -915,7 +934,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
}
}
return (titleLayout.size.width + leftInset + rightInset, { width in
return (titleLayout.size.width + leftInset + titleTrailingInset, { width in
return (CGSize(width: width, height: contentHeight), { animated, inProgress, attemptSynchronous in
let node: ChatMessagePollOptionNode
if let maybeNode = maybeNode {
@ -970,7 +989,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
))
let titleNodeFrame: CGRect
if titleLayout.hasRTL {
titleNodeFrame = CGRect(origin: CGPoint(x: width - rightInset - titleLayout.size.width, y: 15.0), size: titleLayout.size)
titleNodeFrame = CGRect(origin: CGPoint(x: width - titleTrailingInset - titleLayout.size.width, y: 15.0), size: titleLayout.size)
} else {
titleNodeFrame = CGRect(origin: CGPoint(x: leftInset, y: 15.0), size: titleLayout.size)
}
@ -1042,13 +1061,6 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
node.countImage = updatedCountImage
}
let recentVoterPeers: [Peer]
if let optionResult {
recentVoterPeers = optionResult.recentVoterPeerIds.compactMap { message.peers[$0] }
} else {
recentVoterPeers = []
}
var trailingOriginX = width - rightInset
let mediaFrame: CGRect?
@ -1449,7 +1461,7 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
fontSize: font.pointSize,
textColor: currentTextColor,
accentColor: currentTintColor,
insets: UIEdgeInsets(top: ChatMessagePollAddOptionNode.verticalInset, left: 0.0, bottom: ChatMessagePollAddOptionNode.verticalInset, right: 0.0),
insets: UIEdgeInsets(top: ChatMessagePollAddOptionNode.verticalInset, left: 8.0, bottom: ChatMessagePollAddOptionNode.verticalInset, right: 8.0),
hideKeyboard: self.currentFocusedTextInputIsMedia,
customInputView: nil,
placeholder: NSAttributedString(string: strings.CreatePoll_AddOption, font: font, textColor: currentPlaceholderColor),
@ -1476,10 +1488,9 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
)
}
@discardableResult
private func updateTextFieldLayout(size: CGSize, forceUpdate: Bool) -> CGSize {
private func updateTextFieldLayout(size: CGSize, forceUpdate: Bool) {
guard let component = self.makeTextFieldComponent() else {
return CGSize(width: self.currentTextWidth, height: max(ChatMessagePollAddOptionNode.minHeight, size.height))
return
}
let textFieldSize = self.textField.update(
@ -1487,32 +1498,34 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
component: AnyComponent(component),
environment: {},
forceUpdate: forceUpdate,
containerSize: CGSize(width: self.currentTextWidth, height: 1000.0)
containerSize: CGSize(width: self.currentTextWidth + 16.0, height: 1000.0)
)
if let textFieldView = self.textField.view {
if textFieldView.superview == nil {
self.view.insertSubview(textFieldView, belowSubview: self.leftAccessoryButton.view)
}
textFieldView.frame = CGRect(origin: CGPoint(x: ChatMessagePollAddOptionNode.leftInset - 8.0, y: 0.0), size: CGSize(width: textFieldSize.width, height: size.height))
textFieldView.frame = CGRect(origin: CGPoint(x: ChatMessagePollAddOptionNode.leftInset - 16.0, y: 0.0), size: CGSize(width: textFieldSize.width, height: size.height))
}
return textFieldSize
}
private func handleTextFieldStateUpdated(transition: ComponentTransition) {
guard let secondaryTextColor = self.currentSecondaryTextColor, !self.currentSize.width.isZero else {
guard let secondaryTextColor = self.currentSecondaryTextColor, let font = self.currentFont, !self.currentSize.width.isZero else {
return
}
let updatedText = self._textFieldExternalState.text
let updatedMetrics = ChatMessagePollAddOptionNode.layoutMetrics(constrainedWidth: self.currentSize.width, text: updatedText, attachment: self.currentAttachment, font: font)
let previousText = self.currentTextValue
let previousIsEditing = self.currentIsEditing
let previousMeasuredHeight = self.currentMeasuredHeight ?? self.currentSize.height
let textFieldSize = self.updateTextFieldLayout(size: self.currentSize, forceUpdate: true)
let updatedText = self._textFieldExternalState.text
self.currentTextWidth = updatedMetrics.textWidth
self.updateTextFieldLayout(size: self.currentSize, forceUpdate: true)
let updatedIsEditing = self.textFieldView?.isActive ?? self._textFieldExternalState.isEditing
let updatedMeasuredHeight = max(ChatMessagePollAddOptionNode.minHeight, textFieldSize.height)
let updatedMeasuredHeight = updatedMetrics.contentHeight
self.currentTextValue = updatedText
self.currentIsEditing = updatedIsEditing
@ -1532,8 +1545,12 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
self.updateAttachmentLayout(size: self.currentSize, tintColor: secondaryTextColor)
}
private static func measureContentHeight(text: String, font: UIFont, textWidth: CGFloat) -> CGFloat {
var measureText = text
private static func layoutMetrics(constrainedWidth: CGFloat, text: NSAttributedString, attachment: Attachment?, font: UIFont) -> (textWidth: CGFloat, contentHeight: CGFloat) {
let constrainedWidth = min(268.0, constrainedWidth)
let hasTrailingAccessory = attachment?.media != nil || !text.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
let textWidth = max(1.0, constrainedWidth - ChatMessagePollAddOptionNode.leftInset - ChatMessagePollAddOptionNode.rightInset - (hasTrailingAccessory ? ChatMessagePollAddOptionNode.attachmentInset : 0.0))
var measureText = text.string
if measureText.hasSuffix("\n") || measureText.isEmpty {
measureText += "|"
}
@ -1541,7 +1558,11 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
measureTextNode.maximumNumberOfLines = 0
measureTextNode.attributedText = NSAttributedString(string: measureText, font: font, textColor: .black)
let measureSize = measureTextNode.updateLayout(CGSize(width: textWidth, height: .greatestFiniteMagnitude))
return max(ChatMessagePollAddOptionNode.minHeight, measureSize.height + ChatMessagePollAddOptionNode.verticalInset * 2.0)
return (
textWidth: textWidth,
contentHeight: max(ChatMessagePollAddOptionNode.minHeight, measureSize.height + ChatMessagePollAddOptionNode.verticalInset * 2.0)
)
}
static func asyncLayout(_ maybeNode: ChatMessagePollAddOptionNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ strings: PresentationStrings, _ incoming: Bool, _ focusedTextInputIsMedia: Bool, _ text: NSAttributedString, _ attachment: Attachment?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool) -> ChatMessagePollAddOptionNode))) {
@ -1552,14 +1573,11 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
let placeholderColor = (incoming ? presentationData.theme.theme.chat.message.incoming.secondaryTextColor : presentationData.theme.theme.chat.message.outgoing.secondaryTextColor).withMultipliedAlpha(0.7)
let tintColor = incoming ? presentationData.theme.theme.chat.message.incoming.polls.bar : presentationData.theme.theme.chat.message.outgoing.polls.bar
let hasAttachmentPreview = attachment?.media != nil
let displayAttachButton = !text.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !hasAttachmentPreview
let constrainedWidth = min(268.0, constrainedWidth)
let textWidth = max(1.0, constrainedWidth - ChatMessagePollAddOptionNode.leftInset - ChatMessagePollAddOptionNode.rightInset - (hasAttachmentPreview || displayAttachButton ? ChatMessagePollAddOptionNode.attachmentInset : 0.0))
let contentHeight = ChatMessagePollAddOptionNode.measureContentHeight(text: text.string, font: font, textWidth: textWidth)
let metrics = ChatMessagePollAddOptionNode.layoutMetrics(constrainedWidth: constrainedWidth, text: text, attachment: attachment, font: font)
return (constrainedWidth, { width in
let size = CGSize(width: width, height: contentHeight)
let size = CGSize(width: width, height: metrics.contentHeight)
return (size, { _, _ in
let node = maybeNode ?? ChatMessagePollAddOptionNode()
@ -1573,20 +1591,20 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
node.addIconNode.image = generateTintedImage(image: PresentationResourcesChat.chatPollAddIcon(presentationData.theme.theme), color: secondaryTextColor.withMultipliedAlpha(0.7))
}
node.currentTextWidth = textWidth
node.currentTextWidth = metrics.textWidth
node.currentSize = size
node.currentAttachment = attachment
node.currentContext = context
node.currentTheme = presentationData.theme.theme
node.currentIncoming = incoming
node.currentFocusedTextInputIsMedia = focusedTextInputIsMedia
let textFieldSize = node.updateTextFieldLayout(size: size, forceUpdate: false)
node.currentMeasuredHeight = max(ChatMessagePollAddOptionNode.minHeight, textFieldSize.height)
node.currentTextValue = node._textFieldExternalState.text
node.updateTextFieldLayout(size: size, forceUpdate: false)
node.currentMeasuredHeight = metrics.contentHeight
node.currentTextValue = text
node.currentIsEditing = node.textFieldView?.isActive ?? node._textFieldExternalState.isEditing
node.leftAccessoryButton.frame = CGRect(origin: .zero, size: CGSize(width: ChatMessagePollAddOptionNode.leftInset, height: contentHeight))
node.separatorNode.frame = CGRect(origin: CGPoint(x: ChatMessagePollAddOptionNode.leftInset, y: contentHeight - UIScreenPixel), size: CGSize(width: width - ChatMessagePollAddOptionNode.leftInset - 10.0, height: UIScreenPixel))
node.leftAccessoryButton.frame = CGRect(origin: .zero, size: CGSize(width: ChatMessagePollAddOptionNode.leftInset, height: size.height))
node.separatorNode.frame = CGRect(origin: CGPoint(x: ChatMessagePollAddOptionNode.leftInset, y: size.height - UIScreenPixel), size: CGSize(width: width - ChatMessagePollAddOptionNode.leftInset - 10.0, height: UIScreenPixel))
node.separatorNode.backgroundColor = incoming ? presentationData.theme.theme.chat.message.incoming.polls.separator : presentationData.theme.theme.chat.message.outgoing.polls.separator
node.updateModeSelectorLayout(size: size, theme: presentationData.theme.theme, animated: false)
node.updateAttachmentLayout(size: size, tintColor: secondaryTextColor)
@ -1691,7 +1709,7 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode {
let imageNodeSize = CGSize(width: 40.0, height: 40.0)
let imageNodeFrame = CGRect(origin: CGPoint(x: size.width - 10.0 - imageNodeSize.width, y: size.height - ChatMessagePollAddOptionNode.minHeight + floor((ChatMessagePollAddOptionNode.minHeight - imageNodeSize.height) * 0.5)), size: imageNodeSize)
let shouldShowAttachButton = self.currentAttachment?.media == nil && self.text.length != 0
let shouldShowAttachButton = self.currentAttachment?.media == nil && !self.text.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
if shouldShowAttachButton {
let attachButton: HighlightableButtonNode
if let current = self.attachButton {
@ -2318,7 +2336,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
}
let canAlwaysViewResults = poll.isCreator
let canAlwaysViewResults = poll.isCreator
if !hasSelection || (canAlwaysViewResults && selectedOpaqueIdentifiers.isEmpty) {
if !Namespaces.Message.allNonRegular.contains(item.message.id.namespace) {
switch poll.publicity {
@ -2746,7 +2764,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
}
boundingSize.width = max(boundingSize.width, min(270.0, constrainedSize.width))
boundingSize.width = max(boundingSize.width, min(280.0, constrainedSize.width))
var canVote = false
if (item.message.id.namespace == Namespaces.Message.Cloud || Namespaces.Message.allNonRegular.contains(item.message.id.namespace)), let poll = poll, poll.pollId.namespace == Namespaces.Media.CloudPoll, !isClosed {
@ -3182,8 +3200,8 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
buttonWidth = strongSelf.buttonSaveTextNode.frame.width
} else if !strongSelf.buttonViewResultsTextNode.isHidden {
buttonWidth = strongSelf.buttonViewResultsTextNode.frame.width
} else if !strongSelf.buttonSubmitActiveTextNode.isHidden {
buttonWidth = strongSelf.buttonSubmitActiveTextNode.frame.width
} else if !strongSelf.buttonSubmitActiveTextNode.isHidden || !strongSelf.buttonSubmitInactiveTextNode.isHidden {
buttonWidth = max(strongSelf.buttonSubmitActiveTextNode.frame.width, strongSelf.buttonSubmitInactiveTextNode.frame.width)
}
buttonWidth = floor(buttonWidth * 1.1)
strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonWidth) / 2.0), y: verticalOffset), size: CGSize(width: buttonWidth, height: 44.0))

View file

@ -849,6 +849,8 @@ final class ComposePollScreenComponent: Component {
return
}
self.endEditing(true)
guard replace || !self.openAttachMediaContextMenu(subject: subject) else {
return
}