Various improvements

This commit is contained in:
Ali 2023-10-26 15:43:40 +04:00
parent 107b5bf63a
commit a954e9b31c
7 changed files with 114 additions and 27 deletions

View File

@ -590,6 +590,8 @@ extension StoreMessage {
let isForumTopic = (innerFlags & (1 << 3)) != 0 let isForumTopic = (innerFlags & (1 << 3)) != 0
var quote: EngineMessageReplyQuote? var quote: EngineMessageReplyQuote?
let isQuote = (innerFlags & (1 << 9)) != 0
if quoteText != nil || replyMedia != nil { if quoteText != nil || replyMedia != nil {
quote = EngineMessageReplyQuote(text: quoteText ?? "", entities: messageTextEntitiesFromApiEntities(quoteEntities ?? []), media: textMediaAndExpirationTimerFromApiMedia(replyMedia, peerId).media) quote = EngineMessageReplyQuote(text: quoteText ?? "", entities: messageTextEntitiesFromApiEntities(quoteEntities ?? []), media: textMediaAndExpirationTimerFromApiMedia(replyMedia, peerId).media)
} }
@ -626,7 +628,7 @@ extension StoreMessage {
attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId, quote: quote)) attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId, quote: quote))
} }
if let replyHeader = replyHeader { if let replyHeader = replyHeader {
attributes.append(QuotedReplyMessageAttribute(apiHeader: replyHeader, quote: quote)) attributes.append(QuotedReplyMessageAttribute(apiHeader: replyHeader, quote: quote, isQuote: isQuote))
} }
case let .messageReplyStoryHeader(userId, storyId): case let .messageReplyStoryHeader(userId, storyId):
attributes.append(ReplyStoryAttribute(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: storyId))) attributes.append(ReplyStoryAttribute(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: storyId)))
@ -867,8 +869,9 @@ extension StoreMessage {
if let replyTo = replyTo { if let replyTo = replyTo {
var threadMessageId: MessageId? var threadMessageId: MessageId?
switch replyTo { switch replyTo {
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, replyMedia, replyToTopId, quoteText, quoteEntities): case let .messageReplyHeader(innerFlags, replyToMsgId, replyToPeerId, replyHeader, replyMedia, replyToTopId, quoteText, quoteEntities):
var quote: EngineMessageReplyQuote? var quote: EngineMessageReplyQuote?
let isQuote = (innerFlags & (1 << 9)) != 0
if quoteText != nil || replyMedia != nil { if quoteText != nil || replyMedia != nil {
quote = EngineMessageReplyQuote(text: quoteText ?? "", entities: messageTextEntitiesFromApiEntities(quoteEntities ?? []), media: textMediaAndExpirationTimerFromApiMedia(replyMedia, peerId).media) quote = EngineMessageReplyQuote(text: quoteText ?? "", entities: messageTextEntitiesFromApiEntities(quoteEntities ?? []), media: textMediaAndExpirationTimerFromApiMedia(replyMedia, peerId).media)
} }
@ -894,7 +897,7 @@ extension StoreMessage {
} }
attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId, quote: quote)) attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId, quote: quote))
} else if let replyHeader = replyHeader { } else if let replyHeader = replyHeader {
attributes.append(QuotedReplyMessageAttribute(apiHeader: replyHeader, quote: quote)) attributes.append(QuotedReplyMessageAttribute(apiHeader: replyHeader, quote: quote, isQuote: isQuote))
} }
case let .messageReplyStoryHeader(userId, storyId): case let .messageReplyStoryHeader(userId, storyId):
attributes.append(ReplyStoryAttribute(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: storyId))) attributes.append(ReplyStoryAttribute(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: storyId)))

View File

@ -51,6 +51,7 @@ public class QuotedReplyMessageAttribute: MessageAttribute {
public let peerId: PeerId? public let peerId: PeerId?
public let authorName: String? public let authorName: String?
public let quote: EngineMessageReplyQuote? public let quote: EngineMessageReplyQuote?
public let isQuote: Bool
public var associatedMessageIds: [MessageId] { public var associatedMessageIds: [MessageId] {
return [] return []
@ -64,16 +65,18 @@ public class QuotedReplyMessageAttribute: MessageAttribute {
} }
} }
public init(peerId: PeerId?, authorName: String?, quote: EngineMessageReplyQuote?) { public init(peerId: PeerId?, authorName: String?, quote: EngineMessageReplyQuote?, isQuote: Bool) {
self.peerId = peerId self.peerId = peerId
self.authorName = authorName self.authorName = authorName
self.quote = quote self.quote = quote
self.isQuote = isQuote
} }
required public init(decoder: PostboxDecoder) { required public init(decoder: PostboxDecoder) {
self.peerId = decoder.decodeOptionalInt64ForKey("p").flatMap(PeerId.init) self.peerId = decoder.decodeOptionalInt64ForKey("p").flatMap(PeerId.init)
self.authorName = decoder.decodeOptionalStringForKey("a") self.authorName = decoder.decodeOptionalStringForKey("a")
self.quote = decoder.decodeCodable(EngineMessageReplyQuote.self, forKey: "qu") self.quote = decoder.decodeCodable(EngineMessageReplyQuote.self, forKey: "qu")
self.isQuote = decoder.decodeBoolForKey("iq", orElse: true)
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
@ -94,14 +97,16 @@ public class QuotedReplyMessageAttribute: MessageAttribute {
} else { } else {
encoder.encodeNil(forKey: "qu") encoder.encodeNil(forKey: "qu")
} }
encoder.encodeBool(self.isQuote, forKey: "iq")
} }
} }
extension QuotedReplyMessageAttribute { extension QuotedReplyMessageAttribute {
convenience init(apiHeader: Api.MessageFwdHeader, quote: EngineMessageReplyQuote?) { convenience init(apiHeader: Api.MessageFwdHeader, quote: EngineMessageReplyQuote?, isQuote: Bool) {
switch apiHeader { switch apiHeader {
case let .messageFwdHeader(_, fromId, fromName, _, _, _, _, _, _): case let .messageFwdHeader(_, fromId, fromName, _, _, _, _, _, _):
self.init(peerId: fromId?.peerId, authorName: fromName, quote: quote) self.init(peerId: fromId?.peerId, authorName: fromName, quote: quote, isQuote: isQuote)
} }
} }
} }

View File

@ -19,6 +19,7 @@
@property (nonatomic, copy) bool (^ _Nullable shouldRespondToAction)(SEL _Nullable); @property (nonatomic, copy) bool (^ _Nullable shouldRespondToAction)(SEL _Nullable);
@property (nonatomic, copy) bool (^ _Nullable shouldReturn)(); @property (nonatomic, copy) bool (^ _Nullable shouldReturn)();
@property (nonatomic, copy) void (^ _Nullable backspaceWhileEmpty)(); @property (nonatomic, copy) void (^ _Nullable backspaceWhileEmpty)();
@property (nonatomic, copy) void (^ _Nullable dropAutocorrectioniOS16)();
- (instancetype _Nonnull)initWithFrame:(CGRect)frame textContainer:(NSTextContainer * _Nullable)textContainer disableTiling:(bool)disableTiling; - (instancetype _Nonnull)initWithFrame:(CGRect)frame textContainer:(NSTextContainer * _Nullable)textContainer disableTiling:(bool)disableTiling;

View File

@ -12,6 +12,12 @@
@end @end
@interface ChatInputTextViewImpl () <UIGestureRecognizerDelegate> {
UIGestureRecognizer *_tapRecognizer;
}
@end
@implementation ChatInputTextViewImpl @implementation ChatInputTextViewImpl
- (instancetype _Nonnull)initWithFrame:(CGRect)frame textContainer:(NSTextContainer * _Nullable)textContainer disableTiling:(bool)disableTiling { - (instancetype _Nonnull)initWithFrame:(CGRect)frame textContainer:(NSTextContainer * _Nullable)textContainer disableTiling:(bool)disableTiling {
@ -26,10 +32,40 @@
#pragma clang diagnostic pop #pragma clang diagnostic pop
} }
} }
if (@available(iOS 17.0, *)) {
} else {
_tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(workaroundTapGesture:)];
_tapRecognizer.cancelsTouchesInView = false;
_tapRecognizer.delaysTouchesBegan = false;
_tapRecognizer.delaysTouchesEnded = false;
_tapRecognizer.delegate = self;
[self addGestureRecognizer:_tapRecognizer];
}
} }
return self; return self;
} }
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return true;
}
- (void)workaroundTapGesture:(UITapGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateEnded) {
static Class promptClass = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
promptClass = NSClassFromString([[NSString alloc] initWithFormat:@"%@AutocorrectInlinePrompt", @"UI"]);
});
UIView *result = [self hitTest:[recognizer locationInView:self] withEvent:nil];
if (result != nil && [result class] == promptClass) {
if (_dropAutocorrectioniOS16) {
_dropAutocorrectioniOS16();
}
}
}
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{ {
if (_shouldRespondToAction) { if (_shouldRespondToAction) {

View File

@ -32,7 +32,6 @@ open class ChatInputTextNode: ASDisplayNode, UITextViewDelegate {
} }
private var selectionChangedForEditedText: Bool = false private var selectionChangedForEditedText: Bool = false
private var isPreservingSelection: Bool = false
public var textView: ChatInputTextView { public var textView: ChatInputTextView {
return self.view as! ChatInputTextView return self.view as! ChatInputTextView
@ -81,20 +80,7 @@ open class ChatInputTextNode: ASDisplayNode, UITextViewDelegate {
get { get {
return self.textView.attributedText return self.textView.attributedText
} set(value) { } set(value) {
if self.textView.attributedText != value { self.textView.attributedText = value
let selectedRange = self.textView.selectedRange;
let preserveSelectedRange = selectedRange.location != self.textView.textStorage.length
self.textView.attributedText = value ?? NSAttributedString()
if preserveSelectedRange {
self.isPreservingSelection = true
self.textView.selectedRange = selectedRange
self.isPreservingSelection = false
}
self.textView.updateTextContainerInset()
}
} }
} }
@ -164,7 +150,7 @@ open class ChatInputTextNode: ASDisplayNode, UITextViewDelegate {
} }
@objc public func textViewDidChangeSelection(_ textView: UITextView) { @objc public func textViewDidChangeSelection(_ textView: UITextView) {
if self.isPreservingSelection { if self.textView.isPreservingSelection {
return return
} }
@ -187,6 +173,9 @@ open class ChatInputTextNode: ASDisplayNode, UITextViewDelegate {
guard let delegate = self.delegate else { guard let delegate = self.delegate else {
return true return true
} }
if self.textView.isPreservingText {
return false
}
return delegate.chatInputTextNode(shouldChangeTextIn: range, replacementText: text) return delegate.chatInputTextNode(shouldChangeTextIn: range, replacementText: text)
} }
@ -306,6 +295,30 @@ public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDele
} }
} }
override public var attributedText: NSAttributedString? {
get {
return super.attributedText
} set(value) {
if self.attributedText != value {
let selectedRange = self.selectedRange
let preserveSelectedRange = selectedRange.location != self.textStorage.length
super.attributedText = value ?? NSAttributedString()
if preserveSelectedRange {
self.isPreservingSelection = true
self.selectedRange = selectedRange
self.isPreservingSelection = false
}
self.updateTextContainerInset()
}
}
}
fileprivate var isPreservingSelection: Bool = false
fileprivate var isPreservingText: Bool = false
public weak var customDelegate: ChatInputTextNodeDelegate? public weak var customDelegate: ChatInputTextNodeDelegate?
public var theme: Theme? { public var theme: Theme? {
@ -391,6 +404,27 @@ public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDele
self.customTextStorage.delegate = self self.customTextStorage.delegate = self
self.measurementTextStorage.delegate = self self.measurementTextStorage.delegate = self
self.dropAutocorrectioniOS16 = { [weak self] in
guard let self else {
return
}
self.isPreservingSelection = true
self.isPreservingText = true
let rangeCopy = self.selectedRange
var fakeRange = rangeCopy
if fakeRange.location != 0 {
fakeRange.location -= 1
}
self.unmarkText()
self.selectedRange = fakeRange
self.selectedRange = rangeCopy
self.isPreservingSelection = false
self.isPreservingText = false
}
self.shouldCopy = { [weak self] in self.shouldCopy = { [weak self] in
guard let self else { guard let self else {
return true return true
@ -542,7 +576,7 @@ public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDele
if self.measurementTextStorage != self.attributedText || self.measurementTextContainer.size != measureSize || self.measurementTextContainer.rightInset != rightInset { if self.measurementTextStorage != self.attributedText || self.measurementTextContainer.size != measureSize || self.measurementTextContainer.rightInset != rightInset {
self.measurementTextContainer.rightInset = rightInset self.measurementTextContainer.rightInset = rightInset
self.measurementTextStorage.setAttributedString(self.attributedText) self.measurementTextStorage.setAttributedString(self.attributedText ?? NSAttributedString())
self.measurementTextContainer.size = measureSize self.measurementTextContainer.size = measureSize
self.measurementLayoutManager.invalidateLayout(forCharacterRange: NSRange(location: 0, length: self.measurementTextStorage.length), actualCharacterRange: nil) self.measurementLayoutManager.invalidateLayout(forCharacterRange: NSRange(location: 0, length: self.measurementTextStorage.length), actualCharacterRange: nil)
self.measurementLayoutManager.ensureLayout(for: self.measurementTextContainer) self.measurementLayoutManager.ensureLayout(for: self.measurementTextContainer)

View File

@ -547,7 +547,15 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
var adjustedConstrainedTextSize = contrainedTextSize var adjustedConstrainedTextSize = contrainedTextSize
var textCutout: TextNodeCutout? var textCutout: TextNodeCutout?
var textCutoutWidth: CGFloat = 0.0 var textCutoutWidth: CGFloat = 0.0
if arguments.quote != nil || arguments.replyForward?.quote != nil {
var isQuote = false
if arguments.quote != nil {
isQuote = true
} else if let replyForward = arguments.replyForward, replyForward.quote != nil, replyForward.isQuote {
isQuote = true
}
if isQuote {
additionalTitleWidth += 10.0 additionalTitleWidth += 10.0
maxTitleNumberOfLines = 2 maxTitleNumberOfLines = 2
maxTextNumberOfLines = 5 maxTextNumberOfLines = 5
@ -770,7 +778,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
animation: animation animation: animation
) )
if arguments.quote != nil || arguments.replyForward?.quote != nil { if isQuote {
let quoteIconView: UIImageView let quoteIconView: UIImageView
if let current = node.quoteIconView { if let current = node.quoteIconView {
quoteIconView = current quoteIconView = current

View File

@ -2849,8 +2849,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
var hasTracking = false var hasTracking = false
var hasTrackingView = false var hasTrackingView = false
if textInputNode.selectedRange.length == 0 && textInputNode.selectedRange.location > 0 { if textInputNode.selectedRange.length == 0, textInputNode.selectedRange.location > 0, let attributedText = textInputNode.textView.attributedText {
let selectedSubstring = textInputNode.textView.attributedText.attributedSubstring(from: NSRange(location: 0, length: textInputNode.selectedRange.location)) let selectedSubstring = attributedText.attributedSubstring(from: NSRange(location: 0, length: textInputNode.selectedRange.location))
if let lastCharacter = selectedSubstring.string.last, String(lastCharacter).isSingleEmoji { if let lastCharacter = selectedSubstring.string.last, String(lastCharacter).isSingleEmoji {
let queryLength = (String(lastCharacter) as NSString).length let queryLength = (String(lastCharacter) as NSString).length
if selectedSubstring.attribute(ChatTextInputAttributes.customEmoji, at: selectedSubstring.length - queryLength, effectiveRange: nil) == nil { if selectedSubstring.attribute(ChatTextInputAttributes.customEmoji, at: selectedSubstring.length - queryLength, effectiveRange: nil) == nil {