diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift index 5102ca2e19..5af2ccc8a7 100644 --- a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift @@ -1724,6 +1724,10 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS return true } + public func chatInputTextNodeShouldRespondToAction(action: Selector) -> Bool { + return true + } + @objc public func editableTextNodeShouldPaste(_ editableTextNode: ASEditableTextNode) -> Bool { return self.chatInputTextNodeShouldPaste() } diff --git a/submodules/Display/Source/TextNode.swift b/submodules/Display/Source/TextNode.swift index 72fddae5d6..4950c12089 100644 --- a/submodules/Display/Source/TextNode.swift +++ b/submodules/Display/Source/TextNode.swift @@ -1296,8 +1296,8 @@ open class TextNode: ASDisplayNode { var constrainedSegmentWidth = constrainedSize.width var additionalOffsetX: CGFloat = 0.0 if segment.isBlockQuote { - constrainedSegmentWidth -= blockQuoteLeftInset + blockQuoteRightInset additionalOffsetX += blockQuoteLeftInset + constrainedSegmentWidth -= additionalOffsetX + blockQuoteLeftInset + blockQuoteRightInset calculatedSegment.additionalWidth += blockQuoteLeftInset + blockQuoteRightInset } @@ -1305,7 +1305,7 @@ open class TextNode: ASDisplayNode { if let title = segment.title { let rawTitleLine = CTLineCreateWithAttributedString(title) - if let titleLine = CTLineCreateTruncatedLine(rawTitleLine, constrainedSegmentWidth + additionalSegmentRightInset, .end, nil) { + if let titleLine = CTLineCreateTruncatedLine(rawTitleLine, constrainedSegmentWidth - additionalSegmentRightInset, .end, nil) { var lineAscent: CGFloat = 0.0 var lineDescent: CGFloat = 0.0 let lineWidth = CTLineGetTypographicBounds(titleLine, &lineAscent, &lineDescent, nil) @@ -1328,7 +1328,7 @@ open class TextNode: ASDisplayNode { } while true { - let lineCharacterCount = CTTypesetterSuggestLineBreak(typesetter, currentLineStartIndex, constrainedSegmentWidth + additionalSegmentRightInset) + let lineCharacterCount = CTTypesetterSuggestLineBreak(typesetter, currentLineStartIndex, constrainedSegmentWidth - additionalSegmentRightInset) if lineCharacterCount != 0 { let line = CTTypesetterCreateLine(typesetter, CFRange(location: currentLineStartIndex, length: lineCharacterCount)) @@ -2149,6 +2149,9 @@ open class TextNode: ASDisplayNode { let lineWidth: CGFloat = 3.0 var blockFrame = blockQuote.frame.offsetBy(dx: offset.x + 2.0, dy: offset.y) + if blockFrame.origin.x + blockFrame.size.width > bounds.width - layout.insets.right - 2.0 - 30.0 { + blockFrame.size.width = bounds.width - layout.insets.right - blockFrame.origin.x - 2.0 + } blockFrame.size.width += 4.0 blockFrame.origin.x -= 2.0 diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 3d0bfa4a68..a4b53115a7 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -728,6 +728,21 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll } } + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let result = super.hitTest(point, with: event) + + if let textSelectionNode = self.textSelectionNode, result === textSelectionNode.view { + let localPoint = self.view.convert(point, to: textSelectionNode.view) + if !textSelectionNode.canBeginSelection(localPoint) { + if let result = self.textNode.view.hitTest(self.view.convert(point, to: self.textNode.view), with: event) { + return result + } + } + } + + return result + } + private func actionForAttributes(_ attributes: [NSAttributedString.Key: Any], _ index: Int) -> GalleryControllerInteractionTapAction? { if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { var concealed = true diff --git a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/PublicHeaders/ChatInputTextViewImpl/ChatInputTextViewImpl.h b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/PublicHeaders/ChatInputTextViewImpl/ChatInputTextViewImpl.h index 5bf0ee7a66..f32b117179 100755 --- a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/PublicHeaders/ChatInputTextViewImpl/ChatInputTextViewImpl.h +++ b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/PublicHeaders/ChatInputTextViewImpl/ChatInputTextViewImpl.h @@ -16,7 +16,7 @@ @property (nonatomic, copy) bool (^ _Nullable shouldCopy)(); @property (nonatomic, copy) bool (^ _Nullable shouldPaste)(); -@property (nonatomic, copy) ChatInputTextViewImplTargetForAction * _Nullable (^ _Nullable targetForActionImpl)(SEL _Nullable); +@property (nonatomic, copy) bool (^ _Nullable shouldRespondToAction)(SEL _Nullable); @property (nonatomic, copy) bool (^ _Nullable shouldReturn)(); @property (nonatomic, copy) void (^ _Nullable backspaceWhileEmpty)(); diff --git a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/Sources/ChatInputTextViewImpl.m b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/Sources/ChatInputTextViewImpl.m index 3f87ea0066..e3b7594264 100755 --- a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/Sources/ChatInputTextViewImpl.m +++ b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/Sources/ChatInputTextViewImpl.m @@ -16,10 +16,9 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { - if (_targetForActionImpl) { - ChatInputTextViewImplTargetForAction *result = _targetForActionImpl(action); - if (result) { - return result.target != nil; + if (_shouldRespondToAction) { + if (!_shouldRespondToAction(action)) { + return false; } } @@ -50,14 +49,7 @@ return [super canPerformAction:action withSender:sender]; } -- (id)targetForAction:(SEL)action withSender:(id)__unused sender -{ - if (_targetForActionImpl) { - ChatInputTextViewImplTargetForAction *result = _targetForActionImpl(action); - if (result) { - return result.target; - } - } +- (id)targetForAction:(SEL)action withSender:(id)__unused sender { return [super targetForAction:action withSender:sender]; } diff --git a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift index e84d282a94..45119d1fb6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift @@ -19,6 +19,8 @@ public protocol ChatInputTextNodeDelegate: AnyObject { func chatInputTextNode(shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool func chatInputTextNodeShouldCopy() -> Bool func chatInputTextNodeShouldPaste() -> Bool + + func chatInputTextNodeShouldRespondToAction(action: Selector) -> Bool } open class ChatInputTextNode: ASDisplayNode, UITextViewDelegate { @@ -125,6 +127,16 @@ open class ChatInputTextNode: ASDisplayNode, UITextViewDelegate { }) self.textView.delegate = self + self.textView.shouldRespondToAction = { [weak self] action in + guard let self, let action else { + return false + } + if let delegate = self.delegate { + return delegate.chatInputTextNodeShouldRespondToAction(action: action) + } else { + return true + } + } } public func resetInitialPrimaryLanguage() { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index d1e46c4999..2f927ee092 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -809,8 +809,8 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { } } else { if let (_, inlineMediaSize) = inlineMediaAndSize { - if actualSize.height < insets.top + inlineMediaEdgeInset + inlineMediaSize.height + inlineMediaEdgeInset { - actualSize.height = insets.top + inlineMediaEdgeInset + inlineMediaSize.height + inlineMediaEdgeInset + if actualSize.height < backgroundInsets.top + inlineMediaEdgeInset + inlineMediaSize.height + inlineMediaEdgeInset { + actualSize.height = backgroundInsets.top + inlineMediaEdgeInset + inlineMediaSize.height + inlineMediaEdgeInset } } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 863866a4bc..2e077398bf 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -1523,6 +1523,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI let (contentNodeMessagesAndClasses, needSeparateContainers, needReactions) = contentNodeMessagesAndClassesForItem(item) var maximumContentWidth = floor(tmpWidth - layoutConstants.bubble.edgeInset * 3.0 - layoutConstants.bubble.contentInsets.left - layoutConstants.bubble.contentInsets.right - avatarInset) + if needsShareButton { + maximumContentWidth -= 10.0 + } var hasInstantVideo = false for contentNodeItemValue in contentNodeMessagesAndClasses { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index f2e672a88b..a2bfba8d7e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -70,6 +70,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { private var textSelectionState: Promise? + private var linkPreviewHighlightText: String? private var linkPreviewOptionsDisposable: Disposable? private var linkPreviewHighlightingNodes: [LinkHighlightingNode] = [] @@ -318,7 +319,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { messageEntities = updatingMedia.entities?.entities ?? [] } - if let translateToLanguage = item.associatedData.translateToLanguage, !item.message.text.isEmpty && incoming { + if let subject = item.associatedData.subject, case .messageOptions = subject { + } else if let translateToLanguage = item.associatedData.translateToLanguage, !item.message.text.isEmpty && incoming { isTranslating = true for attribute in item.message.attributes { if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage { @@ -645,7 +647,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } if options.hasAlternativeLinks { - strongSelf.updateLinkPreviewTextHighlightState(text: options.url) + strongSelf.linkPreviewHighlightText = options.url + strongSelf.updateLinkPreviewTextHighlightState(text: strongSelf.linkPreviewHighlightText) } }) } @@ -653,6 +656,9 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } strongSelf.updateLinkProgressState() + if let linkPreviewHighlightText = strongSelf.linkPreviewHighlightText { + strongSelf.updateLinkPreviewTextHighlightState(text: linkPreviewHighlightText) + } } }) }) @@ -1122,6 +1128,9 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { if item.message.id.peerId.namespace == Namespaces.Peer.SecretChat { enableQuote = false } + if item.message.containsSecretMedia { + enableQuote = false + } textSelectionNode.enableQuote = enableQuote textSelectionNode.enableTranslate = enableOtherActions diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index 7167d73c52..ab6a57d7fa 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -403,9 +403,37 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch }))) } - if selfController.presentationInterfaceState.copyProtectionEnabled || messages.first?.isCopyProtected() == true { - } else if messages.first?.id.peerId.namespace == Namespaces.Peer.SecretChat { - } else { + var canReplyInAnotherChat = true + + if let message = messages.first { + if selfController.presentationInterfaceState.copyProtectionEnabled { + canReplyInAnotherChat = false + } + + var isAction = false + for media in message.media { + if media is TelegramMediaAction || media is TelegramMediaExpiredContent { + isAction = true + } else if let story = media as? TelegramMediaStory { + if story.isMention { + isAction = true + } + } + } + + if isAction { + canReplyInAnotherChat = false + } + if message.isCopyProtected() { + canReplyInAnotherChat = false + } + if message.id.peerId.namespace == Namespaces.Peer.SecretChat { + canReplyInAnotherChat = false + } + + } + + if canReplyInAnotherChat { //TODO:localize items.append(.action(ContextMenuActionItem(text: "Reply in Another Chat", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replace"), color: theme.contextMenu.primaryColor) }, action: { [weak selfController] c, f in f(.default) @@ -627,7 +655,9 @@ func moveReplyMessageToAnotherChat(selfController: ChatControllerImpl, replySubj proceed(chatController) }) } else { - proceed(ChatControllerImpl(context: selfController.context, chatLocation: .peer(id: peerId))) + let chatController = ChatControllerImpl(context: selfController.context, chatLocation: .peer(id: peerId)) + chatController.activateInput(type: .text) + proceed(chatController) } }) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 77cbe3d830..1f45c93041 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -8957,7 +8957,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.openStickers(beginWithEmoji: false) strongSelf.mediaRecordingModeTooltipController?.dismissImmediately() }, editMessage: { [weak self] in - if let strongSelf = self, let editMessage = strongSelf.presentationInterfaceState.interfaceState.editMessage { + guard let strongSelf = self, let editMessage = strongSelf.presentationInterfaceState.interfaceState.editMessage else { + return + } + + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: editMessage.messageId)) + |> deliverOnMainQueue).start(next: { [weak strongSelf] message in + guard let strongSelf, let message else { + return + } + var disableUrlPreview = false var webpage: TelegramMediaWebpage? @@ -8972,6 +8981,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let text = trimChatInputText(convertMarkdownToAttributes(editMessage.inputState.inputText)) + let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text)) var entitiesAttribute: TextEntitiesMessageAttribute? if !entities.isEmpty { @@ -9015,6 +9025,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } + if text.length == 0 { + if strongSelf.presentationInterfaceState.editMessageState?.mediaReference != nil { + } else if message.media.contains(where: { media in + switch media { + case _ as TelegramMediaImage, _ as TelegramMediaFile, _ as TelegramMediaMap: + return true + default: + return false + } + }) { + } else { + if strongSelf.recordingModeFeedback == nil { + strongSelf.recordingModeFeedback = HapticFeedback() + strongSelf.recordingModeFeedback?.prepareError() + } + strongSelf.recordingModeFeedback?.error() + return + } + } + var updatingMedia = false let media: RequestEditMessageMedia if let editMediaReference = strongSelf.presentationInterfaceState.editMessageState?.mediaReference { @@ -9047,7 +9077,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } }) - } + }) }, beginMessageSearch: { [weak self] domain, query in guard let strongSelf = self else { return diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index c161584f93..b3c1e279ff 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -3680,13 +3680,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch return UIMenu(children: []) } - /*if "".isEmpty { - let index = self.suggestedActionCounter % suggestedActions.count - self.suggestedActionCounter += 1 - print("action index: \(index)") - return UIMenu(children: [suggestedActions[index]]) - }*/ - var actions = suggestedActions if #available(iOS 16.0, *) { @@ -3700,8 +3693,18 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } if editableTextNode.attributedText == nil || editableTextNode.attributedText!.length == 0 || editableTextNode.selectedRange.length == 0 { - } else { + var hasQuoteSelected = false + let selectedRange = editableTextNode.selectedRange + if let attributedText = editableTextNode.attributedText, selectedRange.lowerBound >= 0, selectedRange.lowerBound < attributedText.length, selectedRange.upperBound >= 0, selectedRange.upperBound < attributedText.length { + attributedText.enumerateAttribute(ChatTextInputAttributes.quote, in: selectedRange, using: { value, _, _ in + if value is ChatTextInputTextQuoteAttribute { + hasQuoteSelected = true + } + }) + } + let _ = hasQuoteSelected + var children: [UIAction] = [] //TODO:localize @@ -3920,6 +3923,18 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch return self.chatInputTextNodeShouldCopy() } + public func chatInputTextNodeShouldRespondToAction(action: Selector) -> Bool { + guard let textInputNode = self.textInputNode else { + return true + } + + /*if textInputNode.attributedText == nil || textInputNode.attributedText!.length == 0 || textInputNode.selectedRange.length == 0 { + print("action: \(action)") + }*/ + + return true + } + func chatInputTextNodeShouldPaste() -> Bool { let pasteboard = UIPasteboard.general