mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Emoji fixes
This commit is contained in:
parent
89ecb672c7
commit
2a5b45883d
@ -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 test = NSMutableAttributedString(attributedString: sourceString)
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
test.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: NSRange(location: 0, length: sourceString.length), using: { value, range, _ in
|
||||
if let value = value as? ChatTextInputTextCustomEmojiAttribute {
|
||||
test.addAttribute(NSAttributedString.Key.link, value: URL(string: "tg://emoji?\(value.fileId)")!, range: range)
|
||||
}
|
||||
})
|
||||
}
|
||||
var index = 0
|
||||
test.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: NSRange(location: 0, length: sourceString.length), using: { value, range, _ in
|
||||
if let value = value as? ChatTextInputTextCustomEmojiAttribute {
|
||||
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 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) {
|
||||
let updatedString = NSMutableAttributedString(attributedString: attributedString)
|
||||
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 fileId = Int64(query) {
|
||||
if let url = value as? URL, url.scheme == "tg", url.host == "emoji" {
|
||||
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.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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -306,31 +306,28 @@ public final class GifPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
private(set) var displayPlaceholder: Bool = false {
|
||||
didSet {
|
||||
if self.displayPlaceholder != oldValue {
|
||||
self.onUpdateDisplayPlaceholder(self.displayPlaceholder)
|
||||
}
|
||||
}
|
||||
}
|
||||
let onUpdateDisplayPlaceholder: (Bool) -> Void
|
||||
private(set) var displayPlaceholder: Bool = false
|
||||
let onUpdateDisplayPlaceholder: (Bool, Double) -> Void
|
||||
|
||||
init(
|
||||
item: Item?,
|
||||
context: AccountContext,
|
||||
groupId: String,
|
||||
attemptSynchronousLoad: Bool,
|
||||
onUpdateDisplayPlaceholder: @escaping (Bool) -> Void
|
||||
onUpdateDisplayPlaceholder: @escaping (Bool, Double) -> Void
|
||||
) {
|
||||
self.item = item
|
||||
self.onUpdateDisplayPlaceholder = onUpdateDisplayPlaceholder
|
||||
|
||||
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?.updateDisplayPlaceholder(displayPlaceholder: false)
|
||||
let _ = self
|
||||
//self?.updateDisplayPlaceholder(displayPlaceholder: false, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
@ -359,8 +356,12 @@ public final class GifPagerContentComponent: Component {
|
||||
self.shouldBeAnimating = shouldBePlaying
|
||||
}
|
||||
|
||||
func updateDisplayPlaceholder(displayPlaceholder: Bool) {
|
||||
func updateDisplayPlaceholder(displayPlaceholder: Bool, duration: Double) {
|
||||
if self.displayPlaceholder == displayPlaceholder {
|
||||
return
|
||||
}
|
||||
self.displayPlaceholder = displayPlaceholder
|
||||
self.onUpdateDisplayPlaceholder(displayPlaceholder, duration)
|
||||
}
|
||||
}
|
||||
|
||||
@ -402,6 +403,7 @@ public final class GifPagerContentComponent: Component {
|
||||
|
||||
private let scrollView: ContentScrollView
|
||||
|
||||
private let placeholdersContainerView: UIView
|
||||
private var visibleItemPlaceholderViews: [ItemKey: ItemPlaceholderView] = [:]
|
||||
private var visibleItemLayers: [ItemKey: ItemLayer] = [:]
|
||||
private var ignoreScrolling: Bool = false
|
||||
@ -417,6 +419,8 @@ public final class GifPagerContentComponent: Component {
|
||||
self.shimmerHostView = PortalSourceView()
|
||||
self.standaloneShimmerEffect = StandaloneShimmerEffect()
|
||||
|
||||
self.placeholdersContainerView = UIView()
|
||||
|
||||
self.scrollView = ContentScrollView()
|
||||
|
||||
super.init(frame: frame)
|
||||
@ -436,6 +440,8 @@ public final class GifPagerContentComponent: Component {
|
||||
self.scrollView.delegate = self
|
||||
self.addSubview(self.scrollView)
|
||||
|
||||
self.scrollView.addSubview(self.placeholdersContainerView)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
|
||||
self.useSublayerTransformForActivation = false
|
||||
@ -621,6 +627,12 @@ public final class GifPagerContentComponent: Component {
|
||||
continue
|
||||
}
|
||||
|
||||
if !component.isLoading {
|
||||
if let placeholderView = self.visibleItemPlaceholderViews.removeValue(forKey: .placeholder(index)) {
|
||||
self.visibleItemPlaceholderViews[itemId] = placeholderView
|
||||
}
|
||||
}
|
||||
|
||||
validIds.insert(itemId)
|
||||
|
||||
let itemFrame = itemLayout.frame(at: index)
|
||||
@ -639,7 +651,7 @@ public final class GifPagerContentComponent: Component {
|
||||
context: component.context,
|
||||
groupId: "savedGif",
|
||||
attemptSynchronousLoad: attemptSynchronousLoads,
|
||||
onUpdateDisplayPlaceholder: { [weak self] displayPlaceholder in
|
||||
onUpdateDisplayPlaceholder: { [weak self] displayPlaceholder, duration in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -651,7 +663,7 @@ public final class GifPagerContentComponent: Component {
|
||||
} else {
|
||||
placeholderView = ItemPlaceholderView(shimmerView: strongSelf.shimmerHostView)
|
||||
strongSelf.visibleItemPlaceholderViews[itemId] = placeholderView
|
||||
strongSelf.scrollView.insertSubview(placeholderView, at: 0)
|
||||
strongSelf.placeholdersContainerView.addSubview(placeholderView)
|
||||
}
|
||||
placeholderView.frame = itemLayer.frame
|
||||
placeholderView.update(size: placeholderView.bounds.size)
|
||||
@ -661,9 +673,20 @@ public final class GifPagerContentComponent: Component {
|
||||
} else {
|
||||
if let placeholderView = strongSelf.visibleItemPlaceholderViews[itemId] {
|
||||
strongSelf.visibleItemPlaceholderViews.removeValue(forKey: itemId)
|
||||
placeholderView.removeFromSuperview()
|
||||
if duration > 0.0 {
|
||||
if let itemLayer = strongSelf.visibleItemLayers[itemId] {
|
||||
itemLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
|
||||
}
|
||||
|
||||
strongSelf.updateShimmerIfNeeded()
|
||||
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)
|
||||
placeholderView.update(size: itemFrame.size)
|
||||
}
|
||||
} else if updateItemLayerPlaceholder {
|
||||
}
|
||||
|
||||
if updateItemLayerPlaceholder {
|
||||
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() {
|
||||
if self.visibleItemPlaceholderViews.isEmpty {
|
||||
if self.placeholdersContainerView.subviews.isEmpty {
|
||||
self.standaloneShimmerEffect.layer = nil
|
||||
} else {
|
||||
self.standaloneShimmerEffect.layer = self.shimmerHostView.layer
|
||||
|
@ -112,13 +112,19 @@ public final class TextNodeWithEntities {
|
||||
if let sourceString = arguments.attributedString {
|
||||
let string = NSMutableAttributedString(attributedString: sourceString)
|
||||
|
||||
let fullRange = NSRange(location: 0, length: string.length)
|
||||
string.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: fullRange, options: [], using: { value, range, _ in
|
||||
if let value = value as? ChatTextInputTextCustomEmojiAttribute {
|
||||
if let font = string.attribute(.font, at: range.location, effectiveRange: nil) as? UIFont {
|
||||
string.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize), range: range)
|
||||
var fullRange = NSRange(location: 0, length: string.length)
|
||||
while true {
|
||||
var found = false
|
||||
string.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: fullRange, options: [], using: { value, range, stop in
|
||||
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(
|
||||
ascent: font.ascender,
|
||||
@ -126,7 +132,7 @@ public final class TextNodeWithEntities {
|
||||
width: itemSize
|
||||
)
|
||||
var callbacks = CTRunDelegateCallbacks(
|
||||
version: kCTRunDelegateVersion1,
|
||||
version: kCTRunDelegateCurrentVersion,
|
||||
dealloc: { dataRef in
|
||||
Unmanaged<RunDelegateData>.fromOpaque(dataRef).release()
|
||||
},
|
||||
@ -143,12 +149,23 @@ public final class TextNodeWithEntities {
|
||||
return data.takeUnretainedValue().width
|
||||
}
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -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 let originalTextAttributeKey = NSAttributedString.Key(rawValue: "Attribute__OriginalText")
|
||||
|
||||
public func stateAttributedStringForText(_ text: NSAttributedString) -> NSAttributedString {
|
||||
let sourceString = NSMutableAttributedString(attributedString: text)
|
||||
while true {
|
||||
@ -205,7 +207,8 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject {
|
||||
|
||||
override public func isEqual(_ object: Any?) -> Bool {
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ swift_library(
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -4,6 +4,7 @@ import UIKit.UIGestureRecognizerSubclass
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import TextFormat
|
||||
|
||||
private func findScrollView(view: UIView?) -> UIScrollView? {
|
||||
if let view = view {
|
||||
@ -494,20 +495,43 @@ public final class TextSelectionNode: ASDisplayNode {
|
||||
}
|
||||
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] = []
|
||||
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()
|
||||
}))
|
||||
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()
|
||||
}))
|
||||
if #available(iOS 15.0, *) {
|
||||
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()
|
||||
}))
|
||||
}
|
||||
@ -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
|
||||
self?.performAction(attributedText, .share)
|
||||
self?.performAction(string, .share)
|
||||
self?.dismissSelection()
|
||||
}))
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user