Emoji fixes

This commit is contained in:
Ali 2022-07-16 02:02:39 +02:00
parent 89ecb672c7
commit 2a5b45883d
6 changed files with 126 additions and 45 deletions

View File

@ -9,13 +9,14 @@ private func rtfStringWithAppliedEntities(_ text: String, entities: [MessageText
let sourceString = stringWithAppliedEntities(text, entities: entities, baseColor: .black, linkColor: .black, baseFont: Font.regular(14.0), linkFont: Font.regular(14.0), boldFont: Font.semibold(14.0), italicFont: Font.italic(14.0), boldItalicFont: Font.semiboldItalic(14.0), fixedFont: Font.monospace(14.0), blockQuoteFont: Font.regular(14.0), underlineLinks: false, external: true, message: nil) let sourceString = stringWithAppliedEntities(text, entities: entities, baseColor: .black, linkColor: .black, baseFont: Font.regular(14.0), linkFont: Font.regular(14.0), boldFont: Font.semibold(14.0), italicFont: Font.italic(14.0), boldItalicFont: Font.semiboldItalic(14.0), fixedFont: Font.monospace(14.0), blockQuoteFont: Font.regular(14.0), underlineLinks: false, external: true, message: nil)
let test = NSMutableAttributedString(attributedString: sourceString) let test = NSMutableAttributedString(attributedString: sourceString)
if #available(iOS 15.0, *) { var index = 0
test.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: NSRange(location: 0, length: sourceString.length), using: { value, range, _ in test.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: NSRange(location: 0, length: sourceString.length), using: { value, range, _ in
if let value = value as? ChatTextInputTextCustomEmojiAttribute { if let value = value as? ChatTextInputTextCustomEmojiAttribute {
test.addAttribute(NSAttributedString.Key.link, value: URL(string: "tg://emoji?\(value.fileId)")!, range: range) test.addAttribute(NSAttributedString.Key.link, value: URL(string: "tg://emoji?id=\(value.fileId)&t=\(index)")!, range: range)
} index += 1
}) }
} })
test.removeAttribute(ChatTextInputAttributes.customEmoji, range: NSRange(location: 0, length: test.length))
if let data = try? test.data(from: NSRange(location: 0, length: test.length), documentAttributes: [NSAttributedString.DocumentAttributeKey.documentType: NSAttributedString.DocumentType.rtf]) { if let data = try? test.data(from: NSRange(location: 0, length: test.length), documentAttributes: [NSAttributedString.DocumentAttributeKey.documentType: NSAttributedString.DocumentType.rtf]) {
if var rtf = String(data: data, encoding: .windowsCP1252) { if var rtf = String(data: data, encoding: .windowsCP1252) {
@ -71,10 +72,18 @@ public func chatInputStateStringFromRTF(_ data: Data, type: NSAttributedString.D
if let attributedString = try? NSAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType: type], documentAttributes: nil) { if let attributedString = try? NSAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType: type], documentAttributes: nil) {
let updatedString = NSMutableAttributedString(attributedString: attributedString) let updatedString = NSMutableAttributedString(attributedString: attributedString)
updatedString.enumerateAttribute(NSAttributedString.Key.link, in: NSRange(location: 0, length: attributedString.length), using: { value, range, _ in updatedString.enumerateAttribute(NSAttributedString.Key.link, in: NSRange(location: 0, length: attributedString.length), using: { value, range, _ in
if let url = value as? URL, url.scheme == "tg", url.host == "emoji", let query = url.query { if let url = value as? URL, url.scheme == "tg", url.host == "emoji" {
if let fileId = Int64(query) { var emojiId: Int64?
if let queryItems = URLComponents(string: url.absoluteString)?.queryItems {
for item in queryItems {
if item.name == "id" {
emojiId = item.value.flatMap(Int64.init)
}
}
}
if let emojiId = emojiId {
updatedString.removeAttribute(NSAttributedString.Key.link, range: range) updatedString.removeAttribute(NSAttributedString.Key.link, range: range)
updatedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(stickerPack: nil, fileId: fileId, file: nil), range: range) updatedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(stickerPack: nil, fileId: emojiId, file: nil), range: range)
} }
} }
}) })

View File

@ -306,31 +306,28 @@ public final class GifPagerContentComponent: Component {
} }
} }
} }
private(set) var displayPlaceholder: Bool = false { private(set) var displayPlaceholder: Bool = false
didSet { let onUpdateDisplayPlaceholder: (Bool, Double) -> Void
if self.displayPlaceholder != oldValue {
self.onUpdateDisplayPlaceholder(self.displayPlaceholder)
}
}
}
let onUpdateDisplayPlaceholder: (Bool) -> Void
init( init(
item: Item?, item: Item?,
context: AccountContext, context: AccountContext,
groupId: String, groupId: String,
attemptSynchronousLoad: Bool, attemptSynchronousLoad: Bool,
onUpdateDisplayPlaceholder: @escaping (Bool) -> Void onUpdateDisplayPlaceholder: @escaping (Bool, Double) -> Void
) { ) {
self.item = item self.item = item
self.onUpdateDisplayPlaceholder = onUpdateDisplayPlaceholder self.onUpdateDisplayPlaceholder = onUpdateDisplayPlaceholder
super.init(context: context, file: item?.file, synchronousLoad: attemptSynchronousLoad) super.init(context: context, file: item?.file, synchronousLoad: attemptSynchronousLoad)
self.updateDisplayPlaceholder(displayPlaceholder: true) if item == nil {
self.updateDisplayPlaceholder(displayPlaceholder: true, duration: 0.0)
}
self.started = { [weak self] in self.started = { [weak self] in
self?.updateDisplayPlaceholder(displayPlaceholder: false) let _ = self
//self?.updateDisplayPlaceholder(displayPlaceholder: false, duration: 0.2)
} }
} }
@ -359,8 +356,12 @@ public final class GifPagerContentComponent: Component {
self.shouldBeAnimating = shouldBePlaying self.shouldBeAnimating = shouldBePlaying
} }
func updateDisplayPlaceholder(displayPlaceholder: Bool) { func updateDisplayPlaceholder(displayPlaceholder: Bool, duration: Double) {
if self.displayPlaceholder == displayPlaceholder {
return
}
self.displayPlaceholder = displayPlaceholder self.displayPlaceholder = displayPlaceholder
self.onUpdateDisplayPlaceholder(displayPlaceholder, duration)
} }
} }
@ -402,6 +403,7 @@ public final class GifPagerContentComponent: Component {
private let scrollView: ContentScrollView private let scrollView: ContentScrollView
private let placeholdersContainerView: UIView
private var visibleItemPlaceholderViews: [ItemKey: ItemPlaceholderView] = [:] private var visibleItemPlaceholderViews: [ItemKey: ItemPlaceholderView] = [:]
private var visibleItemLayers: [ItemKey: ItemLayer] = [:] private var visibleItemLayers: [ItemKey: ItemLayer] = [:]
private var ignoreScrolling: Bool = false private var ignoreScrolling: Bool = false
@ -417,6 +419,8 @@ public final class GifPagerContentComponent: Component {
self.shimmerHostView = PortalSourceView() self.shimmerHostView = PortalSourceView()
self.standaloneShimmerEffect = StandaloneShimmerEffect() self.standaloneShimmerEffect = StandaloneShimmerEffect()
self.placeholdersContainerView = UIView()
self.scrollView = ContentScrollView() self.scrollView = ContentScrollView()
super.init(frame: frame) super.init(frame: frame)
@ -436,6 +440,8 @@ public final class GifPagerContentComponent: Component {
self.scrollView.delegate = self self.scrollView.delegate = self
self.addSubview(self.scrollView) self.addSubview(self.scrollView)
self.scrollView.addSubview(self.placeholdersContainerView)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
self.useSublayerTransformForActivation = false self.useSublayerTransformForActivation = false
@ -620,6 +626,12 @@ public final class GifPagerContentComponent: Component {
} else { } else {
continue continue
} }
if !component.isLoading {
if let placeholderView = self.visibleItemPlaceholderViews.removeValue(forKey: .placeholder(index)) {
self.visibleItemPlaceholderViews[itemId] = placeholderView
}
}
validIds.insert(itemId) validIds.insert(itemId)
@ -639,7 +651,7 @@ public final class GifPagerContentComponent: Component {
context: component.context, context: component.context,
groupId: "savedGif", groupId: "savedGif",
attemptSynchronousLoad: attemptSynchronousLoads, attemptSynchronousLoad: attemptSynchronousLoads,
onUpdateDisplayPlaceholder: { [weak self] displayPlaceholder in onUpdateDisplayPlaceholder: { [weak self] displayPlaceholder, duration in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -651,7 +663,7 @@ public final class GifPagerContentComponent: Component {
} else { } else {
placeholderView = ItemPlaceholderView(shimmerView: strongSelf.shimmerHostView) placeholderView = ItemPlaceholderView(shimmerView: strongSelf.shimmerHostView)
strongSelf.visibleItemPlaceholderViews[itemId] = placeholderView strongSelf.visibleItemPlaceholderViews[itemId] = placeholderView
strongSelf.scrollView.insertSubview(placeholderView, at: 0) strongSelf.placeholdersContainerView.addSubview(placeholderView)
} }
placeholderView.frame = itemLayer.frame placeholderView.frame = itemLayer.frame
placeholderView.update(size: placeholderView.bounds.size) placeholderView.update(size: placeholderView.bounds.size)
@ -661,9 +673,20 @@ public final class GifPagerContentComponent: Component {
} else { } else {
if let placeholderView = strongSelf.visibleItemPlaceholderViews[itemId] { if let placeholderView = strongSelf.visibleItemPlaceholderViews[itemId] {
strongSelf.visibleItemPlaceholderViews.removeValue(forKey: itemId) strongSelf.visibleItemPlaceholderViews.removeValue(forKey: itemId)
placeholderView.removeFromSuperview() if duration > 0.0 {
if let itemLayer = strongSelf.visibleItemLayers[itemId] {
strongSelf.updateShimmerIfNeeded() itemLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
}
placeholderView.alpha = 0.0
placeholderView.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, completion: { [weak self, weak placeholderView] _ in
placeholderView?.removeFromSuperview()
self?.updateShimmerIfNeeded()
})
} else {
placeholderView.removeFromSuperview()
strongSelf.updateShimmerIfNeeded()
}
} }
} }
} }
@ -683,9 +706,13 @@ public final class GifPagerContentComponent: Component {
itemTransition.setFrame(view: placeholderView, frame: itemFrame) itemTransition.setFrame(view: placeholderView, frame: itemFrame)
placeholderView.update(size: itemFrame.size) placeholderView.update(size: itemFrame.size)
} }
} else if updateItemLayerPlaceholder { }
if updateItemLayerPlaceholder {
if itemLayer.displayPlaceholder { if itemLayer.displayPlaceholder {
itemLayer.onUpdateDisplayPlaceholder(true) itemLayer.onUpdateDisplayPlaceholder(true, 0.0)
} else {
itemLayer.onUpdateDisplayPlaceholder(false, 0.2)
} }
} }
} }
@ -708,7 +735,7 @@ public final class GifPagerContentComponent: Component {
} }
private func updateShimmerIfNeeded() { private func updateShimmerIfNeeded() {
if self.visibleItemPlaceholderViews.isEmpty { if self.placeholdersContainerView.subviews.isEmpty {
self.standaloneShimmerEffect.layer = nil self.standaloneShimmerEffect.layer = nil
} else { } else {
self.standaloneShimmerEffect.layer = self.shimmerHostView.layer self.standaloneShimmerEffect.layer = self.shimmerHostView.layer

View File

@ -112,13 +112,19 @@ public final class TextNodeWithEntities {
if let sourceString = arguments.attributedString { if let sourceString = arguments.attributedString {
let string = NSMutableAttributedString(attributedString: sourceString) let string = NSMutableAttributedString(attributedString: sourceString)
let fullRange = NSRange(location: 0, length: string.length) var fullRange = NSRange(location: 0, length: string.length)
string.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: fullRange, options: [], using: { value, range, _ in while true {
if let value = value as? ChatTextInputTextCustomEmojiAttribute { var found = false
if let font = string.attribute(.font, at: range.location, effectiveRange: nil) as? UIFont { string.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: fullRange, options: [], using: { value, range, stop in
string.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize), range: range) if let value = value as? ChatTextInputTextCustomEmojiAttribute, let font = string.attribute(.font, at: range.location, effectiveRange: nil) as? UIFont {
let updatedSubstring = NSMutableAttributedString(string: ".")
let itemSize = font.pointSize * 24.0 / 17.0 / CGFloat(min(2, range.length)) let replacementRange = NSRange(location: 0, length: updatedSubstring.length)
updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange)
updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize), range: replacementRange)
updatedSubstring.addAttribute(originalTextAttributeKey, value: string.attributedSubstring(from: range).string, range: replacementRange)
let itemSize = font.pointSize * 24.0 / 17.0
let runDelegateData = RunDelegateData( let runDelegateData = RunDelegateData(
ascent: font.ascender, ascent: font.ascender,
@ -126,7 +132,7 @@ public final class TextNodeWithEntities {
width: itemSize width: itemSize
) )
var callbacks = CTRunDelegateCallbacks( var callbacks = CTRunDelegateCallbacks(
version: kCTRunDelegateVersion1, version: kCTRunDelegateCurrentVersion,
dealloc: { dataRef in dealloc: { dataRef in
Unmanaged<RunDelegateData>.fromOpaque(dataRef).release() Unmanaged<RunDelegateData>.fromOpaque(dataRef).release()
}, },
@ -143,12 +149,23 @@ public final class TextNodeWithEntities {
return data.takeUnretainedValue().width return data.takeUnretainedValue().width
} }
) )
if let runDelegate = CTRunDelegateCreate(&callbacks, Unmanaged.passRetained(runDelegateData).toOpaque()) { if let runDelegate = CTRunDelegateCreate(&callbacks, Unmanaged.passRetained(runDelegateData).toOpaque()) {
string.addAttribute(NSAttributedString.Key(kCTRunDelegateAttributeName as String), value: runDelegate, range: range) updatedSubstring.addAttribute(NSAttributedString.Key(kCTRunDelegateAttributeName as String), value: runDelegate, range: replacementRange)
} }
string.replaceCharacters(in: range, with: updatedSubstring)
let updatedRange = NSRange(location: range.location, length: updatedSubstring.length)
found = true
stop.pointee = ObjCBool(true)
fullRange = NSRange(location: updatedRange.upperBound, length: fullRange.upperBound - range.upperBound)
} }
})
if !found {
break
} }
}) }
updatedString = string updatedString = string
} }

View File

@ -23,6 +23,8 @@ public struct ChatTextInputAttributes {
public static let allAttributes = [ChatTextInputAttributes.bold, ChatTextInputAttributes.italic, ChatTextInputAttributes.monospace, ChatTextInputAttributes.strikethrough, ChatTextInputAttributes.underline, ChatTextInputAttributes.textMention, ChatTextInputAttributes.textUrl, ChatTextInputAttributes.spoiler, ChatTextInputAttributes.customEmoji] public static let allAttributes = [ChatTextInputAttributes.bold, ChatTextInputAttributes.italic, ChatTextInputAttributes.monospace, ChatTextInputAttributes.strikethrough, ChatTextInputAttributes.underline, ChatTextInputAttributes.textMention, ChatTextInputAttributes.textUrl, ChatTextInputAttributes.spoiler, ChatTextInputAttributes.customEmoji]
} }
public let originalTextAttributeKey = NSAttributedString.Key(rawValue: "Attribute__OriginalText")
public func stateAttributedStringForText(_ text: NSAttributedString) -> NSAttributedString { public func stateAttributedStringForText(_ text: NSAttributedString) -> NSAttributedString {
let sourceString = NSMutableAttributedString(attributedString: text) let sourceString = NSMutableAttributedString(attributedString: text)
while true { while true {
@ -205,7 +207,8 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject {
override public func isEqual(_ object: Any?) -> Bool { override public func isEqual(_ object: Any?) -> Bool {
if let other = object as? ChatTextInputTextCustomEmojiAttribute { if let other = object as? ChatTextInputTextCustomEmojiAttribute {
return self.stickerPack == other.stickerPack && self.fileId == other.fileId && self.file?.fileId == other.file?.fileId return self === other
//return self.stickerPack == other.stickerPack && self.fileId == other.fileId && self.file?.fileId == other.file?.fileId
} else { } else {
return false return false
} }

View File

@ -13,6 +13,7 @@ swift_library(
"//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display", "//submodules/Display:Display",
"//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/TextFormat:TextFormat",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -4,6 +4,7 @@ import UIKit.UIGestureRecognizerSubclass
import AsyncDisplayKit import AsyncDisplayKit
import Display import Display
import TelegramPresentationData import TelegramPresentationData
import TextFormat
private func findScrollView(view: UIView?) -> UIScrollView? { private func findScrollView(view: UIView?) -> UIScrollView? {
if let view = view { if let view = view {
@ -494,20 +495,43 @@ public final class TextSelectionNode: ASDisplayNode {
} }
completeRect = completeRect.insetBy(dx: 0.0, dy: -12.0) completeRect = completeRect.insetBy(dx: 0.0, dy: -12.0)
let attributedText = attributedString.attributedSubstring(from: range) let string = NSMutableAttributedString(attributedString: attributedString.attributedSubstring(from: range))
var fullRange = NSRange(location: 0, length: string.length)
while true {
var found = false
string.enumerateAttribute(originalTextAttributeKey, in: fullRange, options: [], using: { value, range, stop in
if let value = value as? String {
let updatedSubstring = NSMutableAttributedString(string: value)
let replacementRange = NSRange(location: 0, length: updatedSubstring.length)
updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange)
string.replaceCharacters(in: range, with: updatedSubstring)
let updatedRange = NSRange(location: range.location, length: updatedSubstring.length)
found = true
stop.pointee = ObjCBool(true)
fullRange = NSRange(location: updatedRange.upperBound, length: fullRange.upperBound - range.upperBound)
}
})
if !found {
break
}
}
var actions: [ContextMenuAction] = [] var actions: [ContextMenuAction] = []
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.strings.Conversation_ContextMenuCopy), action: { [weak self] in actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.strings.Conversation_ContextMenuCopy), action: { [weak self] in
self?.performAction(attributedText, .copy) self?.performAction(string, .copy)
self?.dismissSelection() self?.dismissSelection()
})) }))
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuLookUp, accessibilityLabel: self.strings.Conversation_ContextMenuLookUp), action: { [weak self] in actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuLookUp, accessibilityLabel: self.strings.Conversation_ContextMenuLookUp), action: { [weak self] in
self?.performAction(attributedText, .lookup) self?.performAction(string, .lookup)
self?.dismissSelection() self?.dismissSelection()
})) }))
if #available(iOS 15.0, *) { if #available(iOS 15.0, *) {
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuTranslate, accessibilityLabel: self.strings.Conversation_ContextMenuTranslate), action: { [weak self] in actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuTranslate, accessibilityLabel: self.strings.Conversation_ContextMenuTranslate), action: { [weak self] in
self?.performAction(attributedText, .translate) self?.performAction(string, .translate)
self?.dismissSelection() self?.dismissSelection()
})) }))
} }
@ -518,7 +542,7 @@ public final class TextSelectionNode: ASDisplayNode {
// })) // }))
// } // }
actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuShare, accessibilityLabel: self.strings.Conversation_ContextMenuShare), action: { [weak self] in actions.append(ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuShare, accessibilityLabel: self.strings.Conversation_ContextMenuShare), action: { [weak self] in
self?.performAction(attributedText, .share) self?.performAction(string, .share)
self?.dismissSelection() self?.dismissSelection()
})) }))