mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
1f814962ad
commit
82046c886b
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)();
|
||||
|
||||
|
@ -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];
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -70,6 +70,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
private var textSelectionState: Promise<ChatControllerSubject.MessageOptionsInfo.SelectionState>?
|
||||
|
||||
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
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user