mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-02 20:55:48 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
c4b35131a6
@ -761,7 +761,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
self.mainButtonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { _, _ in
|
||||
self.interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { _, _ in
|
||||
}, setupEditMessage: { _, _ in
|
||||
}, beginMessageSelection: { _, _ in
|
||||
}, deleteSelectedMessages: {
|
||||
|
||||
@ -67,7 +67,7 @@ public enum ChatOpenWebViewSource: Equatable {
|
||||
}
|
||||
|
||||
public final class ChatPanelInterfaceInteraction {
|
||||
public let setupReplyMessage: (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void
|
||||
public let setupReplyMessage: (MessageId?, @escaping (ContainedViewLayoutTransition, @escaping () -> Void) -> Void) -> Void
|
||||
public let setupEditMessage: (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void
|
||||
public let beginMessageSelection: ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void
|
||||
public let deleteSelectedMessages: () -> Void
|
||||
@ -173,7 +173,7 @@ public final class ChatPanelInterfaceInteraction {
|
||||
public let statuses: ChatPanelInterfaceInteractionStatuses?
|
||||
|
||||
public init(
|
||||
setupReplyMessage: @escaping (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void,
|
||||
setupReplyMessage: @escaping (MessageId?, @escaping (ContainedViewLayoutTransition, @escaping () -> Void) -> Void) -> Void,
|
||||
setupEditMessage: @escaping (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void,
|
||||
beginMessageSelection: @escaping ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void,
|
||||
deleteSelectedMessages: @escaping () -> Void,
|
||||
|
||||
@ -22,6 +22,7 @@ public protocol ContextControllerProtocol: ViewController {
|
||||
var getOverlayViews: (() -> [UIView])? { get set }
|
||||
|
||||
func dismiss(completion: (() -> Void)?)
|
||||
func dismiss(result: ContextMenuActionResult, completion: (() -> Void)?)
|
||||
|
||||
func getActionsMinHeight() -> ContextController.ActionsHeight?
|
||||
func setItems(_ items: Signal<ContextController.Items, NoError>, minHeight: ContextController.ActionsHeight?, animated: Bool)
|
||||
@ -2601,7 +2602,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
}
|
||||
}
|
||||
|
||||
private func dismiss(result: ContextMenuActionResult, completion: (() -> Void)?) {
|
||||
public func dismiss(result: ContextMenuActionResult, completion: (() -> Void)?) {
|
||||
if viewTreeContainsFirstResponder(view: self.view) {
|
||||
self.dismissOnInputClose = (result, completion)
|
||||
self.view.endEditing(true)
|
||||
|
||||
@ -126,4 +126,8 @@ public final class PeekController: ViewController, ContextControllerProtocol {
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
})
|
||||
}
|
||||
|
||||
public func dismiss(result: ContextMenuActionResult, completion: (() -> Void)?) {
|
||||
self.dismiss(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1133,7 +1133,7 @@ public extension ContainedViewLayoutTransition {
|
||||
previousTransform = layer.transform
|
||||
}
|
||||
layer.transform = transform
|
||||
layer.animate(from: NSValue(caTransform3D: previousTransform), to: NSValue(caTransform3D: transform), keyPath: "transform", timingFunction: curve.timingFunction, duration: duration, mediaTimingFunction: curve.mediaTimingFunction, completion: { value in
|
||||
layer.animate(from: NSValue(caTransform3D: previousTransform), to: NSValue(caTransform3D: transform), keyPath: "transform", timingFunction: curve.timingFunction, duration: duration, delay: delay, mediaTimingFunction: curve.mediaTimingFunction, completion: { value in
|
||||
completion?(value)
|
||||
})
|
||||
}
|
||||
@ -1198,37 +1198,13 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
}
|
||||
|
||||
func updateSublayerTransformScale(node: ASDisplayNode, scale: CGFloat, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
|
||||
func updateSublayerTransformScale(node: ASDisplayNode, scale: CGFloat, delay: Double = 0.0, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
if !node.isNodeLoaded {
|
||||
node.subnodeTransform = CATransform3DMakeScale(scale, scale, 1.0)
|
||||
completion?(true)
|
||||
return
|
||||
}
|
||||
let t = node.layer.sublayerTransform
|
||||
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
|
||||
if currentScale.isEqual(to: scale) {
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch self {
|
||||
case .immediate:
|
||||
node.layer.removeAnimation(forKey: "sublayerTransform")
|
||||
node.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0)
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
case let .animated(duration, curve):
|
||||
node.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0)
|
||||
node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: delay, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: {
|
||||
result in
|
||||
if let completion = completion {
|
||||
completion(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
self.updateSublayerTransformScale(layer: node.layer, scale: CGPoint(x: scale, y: scale), beginWithCurrentState: beginWithCurrentState, completion: completion)
|
||||
}
|
||||
|
||||
func updateSublayerTransformScaleAdditive(node: ASDisplayNode, scale: CGFloat, completion: ((Bool) -> Void)? = nil) {
|
||||
|
||||
@ -2236,7 +2236,9 @@ open class TextNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray
|
||||
|
||||
if glyphRuns.count != 0 {
|
||||
let hasAttachments = !line.attachments.isEmpty
|
||||
for run in glyphRuns {
|
||||
let run = run as! CTRun
|
||||
let glyphCount = CTRunGetGlyphCount(run)
|
||||
@ -2269,13 +2271,45 @@ open class TextNode: ASDisplayNode {
|
||||
if fixDoubleEmoji {
|
||||
context.setBlendMode(.normal)
|
||||
}
|
||||
CTRunDraw(run, context, CFRangeMake(0, glyphCount))
|
||||
|
||||
if hasAttachments {
|
||||
let stringRange = CTRunGetStringRange(run)
|
||||
if line.attachments.contains(where: { $0.range.contains(stringRange.location) }) {
|
||||
} else {
|
||||
CTRunDraw(run, context, CFRangeMake(0, glyphCount))
|
||||
}
|
||||
} else {
|
||||
CTRunDraw(run, context, CFRangeMake(0, glyphCount))
|
||||
}
|
||||
|
||||
if fixDoubleEmoji {
|
||||
context.setBlendMode(blendMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for attachment in line.attachments {
|
||||
let image = attachment.attachment
|
||||
var textColor: UIColor?
|
||||
layout.attributedString?.enumerateAttributes(in: attachment.range, options: []) { attributes, range, _ in
|
||||
if let color = attributes[NSAttributedString.Key.foregroundColor] as? UIColor {
|
||||
textColor = color
|
||||
}
|
||||
}
|
||||
if let textColor {
|
||||
if let tintedImage = generateTintedImage(image: image, color: textColor) {
|
||||
let imageRect = CGRect(origin: CGPoint(x: attachment.frame.midX - tintedImage.size.width * 0.5, y: attachment.frame.midY - tintedImage.size.height * 0.5 + 1.0), size: tintedImage.size).offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)
|
||||
context.translateBy(x: imageRect.midX, y: imageRect.midY)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -imageRect.midX, y: -imageRect.midY)
|
||||
context.draw(tintedImage.cgImage!, in: imageRect)
|
||||
context.translateBy(x: imageRect.midX, y: imageRect.midY)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -imageRect.midX, y: -imageRect.midY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !line.strikethroughs.isEmpty {
|
||||
for strikethrough in line.strikethroughs {
|
||||
guard let lineRange = line.range else {
|
||||
|
||||
@ -331,7 +331,7 @@ public func messageTextEntitiesInRange(entities: [MessageTextEntity], range: NSR
|
||||
}
|
||||
if entity.range.overlaps(range) {
|
||||
var mappedRange = entity.range.clamped(to: range)
|
||||
mappedRange = (entity.range.lowerBound - range.lowerBound) ..< (entity.range.upperBound - range.lowerBound)
|
||||
mappedRange = (mappedRange.lowerBound - range.lowerBound) ..< (mappedRange.upperBound - range.lowerBound)
|
||||
result.append(MessageTextEntity(range: mappedRange, type: entity.type))
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,13 +170,13 @@ public final class ChatBotInfoItemNode: ListViewItemNode {
|
||||
recognizer.tapActionAtPoint = { [weak self] point in
|
||||
if let strongSelf = self {
|
||||
let tapAction = strongSelf.tapActionAtPoint(point, gesture: .tap, isEstimating: true)
|
||||
switch tapAction {
|
||||
case .none:
|
||||
break
|
||||
case .ignore:
|
||||
return .fail
|
||||
case .url, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji:
|
||||
return .waitForSingleTap
|
||||
switch tapAction.content {
|
||||
case .none:
|
||||
break
|
||||
case .ignore:
|
||||
return .fail
|
||||
case .url, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji:
|
||||
return .waitForSingleTap
|
||||
}
|
||||
}
|
||||
|
||||
@ -427,20 +427,20 @@ public final class ChatBotInfoItemNode: ListViewItemNode {
|
||||
if let (attributeText, fullText) = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return .url(url: url, concealed: concealed, activate: nil)
|
||||
return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: concealed)))
|
||||
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
return .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)
|
||||
return ChatMessageBubbleContentTapAction(content: .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false))
|
||||
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
return .textMention(peerName)
|
||||
return ChatMessageBubbleContentTapAction(content: .textMention(peerName))
|
||||
} else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {
|
||||
return .botCommand(botCommand)
|
||||
return ChatMessageBubbleContentTapAction(content: .botCommand(botCommand))
|
||||
} else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
|
||||
return .hashtag(hashtag.peerName, hashtag.hashtag)
|
||||
return ChatMessageBubbleContentTapAction(content: .hashtag(hashtag.peerName, hashtag.hashtag))
|
||||
} else {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
} else {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
}
|
||||
|
||||
@ -449,49 +449,49 @@ public final class ChatBotInfoItemNode: ListViewItemNode {
|
||||
case .ended:
|
||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
||||
switch gesture {
|
||||
case .tap:
|
||||
let tapAction = self.tapActionAtPoint(location, gesture: gesture, isEstimating: false)
|
||||
switch tapAction {
|
||||
case .none, .ignore:
|
||||
break
|
||||
case let .url(url, concealed, activate):
|
||||
self.item?.controllerInteraction.openUrl(url, concealed, nil, nil, activate?())
|
||||
case let .peerMention(peerId, _, _):
|
||||
if let item = self.item {
|
||||
let _ = (item.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
|
||||
if let peer = peer {
|
||||
self?.item?.controllerInteraction.openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
|
||||
}
|
||||
})
|
||||
case .tap:
|
||||
let tapAction = self.tapActionAtPoint(location, gesture: gesture, isEstimating: false)
|
||||
switch tapAction.content {
|
||||
case .none, .ignore:
|
||||
break
|
||||
case let .url(url):
|
||||
self.item?.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url.url, concealed: url.concealed, progress: tapAction.activate?()))
|
||||
case let .peerMention(peerId, _, _):
|
||||
if let item = self.item {
|
||||
let _ = (item.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
|
||||
if let peer = peer {
|
||||
self?.item?.controllerInteraction.openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
|
||||
}
|
||||
case let .textMention(name):
|
||||
self.item?.controllerInteraction.openPeerMention(name)
|
||||
case let .botCommand(command):
|
||||
self.item?.controllerInteraction.sendBotCommand(nil, command)
|
||||
case let .hashtag(peerName, hashtag):
|
||||
self.item?.controllerInteraction.openHashtag(peerName, hashtag)
|
||||
default:
|
||||
break
|
||||
})
|
||||
}
|
||||
case let .textMention(name):
|
||||
self.item?.controllerInteraction.openPeerMention(name)
|
||||
case let .botCommand(command):
|
||||
self.item?.controllerInteraction.sendBotCommand(nil, command)
|
||||
case let .hashtag(peerName, hashtag):
|
||||
self.item?.controllerInteraction.openHashtag(peerName, hashtag)
|
||||
default:
|
||||
break
|
||||
}
|
||||
case .longTap, .doubleTap:
|
||||
if let item = self.item, self.backgroundNode.frame.contains(location) {
|
||||
let tapAction = self.tapActionAtPoint(location, gesture: gesture, isEstimating: false)
|
||||
switch tapAction {
|
||||
case .none, .ignore:
|
||||
break
|
||||
case let .url(url, _, _):
|
||||
item.controllerInteraction.longTap(.url(url), nil)
|
||||
case let .peerMention(peerId, mention, _):
|
||||
item.controllerInteraction.longTap(.peerMention(peerId, mention), nil)
|
||||
case let .textMention(name):
|
||||
item.controllerInteraction.longTap(.mention(name), nil)
|
||||
case let .botCommand(command):
|
||||
item.controllerInteraction.longTap(.command(command), nil)
|
||||
case let .hashtag(_, hashtag):
|
||||
item.controllerInteraction.longTap(.hashtag(hashtag), nil)
|
||||
default:
|
||||
break
|
||||
switch tapAction.content {
|
||||
case .none, .ignore:
|
||||
break
|
||||
case let .url(url):
|
||||
item.controllerInteraction.longTap(.url(url.url), nil)
|
||||
case let .peerMention(peerId, mention, _):
|
||||
item.controllerInteraction.longTap(.peerMention(peerId, mention), nil)
|
||||
case let .textMention(name):
|
||||
item.controllerInteraction.longTap(.mention(name), nil)
|
||||
case let .botCommand(command):
|
||||
item.controllerInteraction.longTap(.command(command), nil)
|
||||
case let .hashtag(_, hashtag):
|
||||
item.controllerInteraction.longTap(.hashtag(hashtag), nil)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
||||
@ -378,7 +378,7 @@ public final class ChatButtonKeyboardInputNode: ChatInputNode {
|
||||
self.controllerInteraction.sendMessage(markupButton.title)
|
||||
dismissIfOnce = true
|
||||
case let .url(url):
|
||||
self.controllerInteraction.openUrl(url, true, nil, nil, nil)
|
||||
self.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: true))
|
||||
case .requestMap:
|
||||
self.controllerInteraction.shareCurrentLocation()
|
||||
case .requestPhone:
|
||||
|
||||
@ -517,29 +517,29 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
if let (attributeText, fullText) = self.labelNode.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return .url(url: url, concealed: concealed, activate: nil)
|
||||
return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: concealed)))
|
||||
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
return .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: true)
|
||||
return ChatMessageBubbleContentTapAction(content: .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: true))
|
||||
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
return .textMention(peerName)
|
||||
return ChatMessageBubbleContentTapAction(content: .textMention(peerName))
|
||||
} else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {
|
||||
return .botCommand(botCommand)
|
||||
return ChatMessageBubbleContentTapAction(content: .botCommand(botCommand))
|
||||
} else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
|
||||
return .hashtag(hashtag.peerName, hashtag.hashtag)
|
||||
return ChatMessageBubbleContentTapAction(content: .hashtag(hashtag.peerName, hashtag.hashtag))
|
||||
}
|
||||
}
|
||||
if let imageNode = imageNode, imageNode.frame.contains(point) {
|
||||
return .openMessage
|
||||
return ChatMessageBubbleContentTapAction(content: .openMessage)
|
||||
}
|
||||
|
||||
if let backgroundNode = self.backgroundNode, backgroundNode.frame.contains(point) {
|
||||
if let item = self.item, item.message.media.contains(where: { $0 is TelegramMediaStory }) {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
} else {
|
||||
return .openMessage
|
||||
return ChatMessageBubbleContentTapAction(content: .openMessage)
|
||||
}
|
||||
} else {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
public var activateAction: (() -> Void)?
|
||||
public var requestUpdateLayout: (() -> Void)?
|
||||
|
||||
public var defaultContentAction: () -> ChatMessageBubbleContentTapAction = { return .none }
|
||||
public var defaultContentAction: () -> ChatMessageBubbleContentTapAction = { return ChatMessageBubbleContentTapAction(content: .none) }
|
||||
|
||||
public var visibility: ListViewItemNodeVisibility = .none {
|
||||
didSet {
|
||||
@ -731,6 +731,12 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
case .actionButton:
|
||||
actualSize.height += buttonBottomSpacing
|
||||
}
|
||||
} else {
|
||||
if let (_, inlineMediaSize) = inlineMediaAndSize {
|
||||
if actualSize.height < insets.top + inlineMediaEdgeInset + inlineMediaSize.height + inlineMediaEdgeInset {
|
||||
actualSize.height = insets.top + inlineMediaEdgeInset + inlineMediaSize.height + inlineMediaEdgeInset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if case let .linear(_, bottom) = position {
|
||||
@ -798,7 +804,10 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
if let (inlineMediaValue, inlineMediaSize) = inlineMediaAndSize {
|
||||
let inlineMediaFrame = CGRect(origin: CGPoint(x: actualSize.width - insets.right - inlineMediaSize.width, y: backgroundInsets.top + inlineMediaEdgeInset), size: inlineMediaSize)
|
||||
var inlineMediaFrame = CGRect(origin: CGPoint(x: actualSize.width - insets.right - inlineMediaSize.width, y: backgroundInsets.top + inlineMediaEdgeInset), size: inlineMediaSize)
|
||||
if contentLayoutOrder.isEmpty {
|
||||
inlineMediaFrame.origin.x = insets.left
|
||||
}
|
||||
|
||||
let inlineMedia: TransformImageNode
|
||||
var updateMedia = false
|
||||
@ -2004,21 +2013,21 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
if let (attributeText, fullText) = text.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return .url(url: url, concealed: concealed, activate: nil)
|
||||
return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: concealed)))
|
||||
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
return .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)
|
||||
return ChatMessageBubbleContentTapAction(content: .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false))
|
||||
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
return .textMention(peerName)
|
||||
return ChatMessageBubbleContentTapAction(content: .textMention(peerName))
|
||||
} else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {
|
||||
return .botCommand(botCommand)
|
||||
return ChatMessageBubbleContentTapAction(content: .botCommand(botCommand))
|
||||
} else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
|
||||
return .hashtag(hashtag.peerName, hashtag.hashtag)
|
||||
return ChatMessageBubbleContentTapAction(content: .hashtag(hashtag.peerName, hashtag.hashtag))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let actionButton = self.actionButton, actionButton.frame.contains(point) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
|
||||
return self.defaultContentAction()
|
||||
@ -2103,9 +2112,9 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
highlightTimer.invalidate()
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: self.isHighlighted ? 0.2 : 0.2, curve: .easeInOut)
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: self.isHighlighted ? 0.3 : 0.2, curve: .easeInOut)
|
||||
let scale: CGFloat = self.isHighlighted ? ((self.bounds.width - 5.0) / self.bounds.width) : 1.0
|
||||
transition.updateSublayerTransformScale(node: self, scale: scale)
|
||||
transition.updateSublayerTransformScale(node: self, scale: scale, beginWithCurrentState: true)
|
||||
}
|
||||
|
||||
public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
|
||||
|
||||
@ -121,26 +121,52 @@ public enum ChatMessageBubblePreparePosition {
|
||||
case mosaic(top: ChatMessageBubbleRelativePosition, bottom: ChatMessageBubbleRelativePosition)
|
||||
}
|
||||
|
||||
public enum ChatMessageBubbleContentTapAction {
|
||||
case none
|
||||
case url(url: String, concealed: Bool, activate: (() -> Promise<Bool>?)?)
|
||||
case textMention(String)
|
||||
case peerMention(peerId: PeerId, mention: String, openProfile: Bool)
|
||||
case botCommand(String)
|
||||
case hashtag(String?, String)
|
||||
case instantPage
|
||||
case wallpaper
|
||||
case theme
|
||||
case call(peerId: PeerId, isVideo: Bool)
|
||||
case openMessage
|
||||
case timecode(Double, String)
|
||||
case tooltip(String, ASDisplayNode?, CGRect?)
|
||||
case bankCard(String)
|
||||
case ignore
|
||||
case openPollResults(Data)
|
||||
case copy(String)
|
||||
case largeEmoji(String, String?, TelegramMediaFile)
|
||||
case customEmoji(TelegramMediaFile)
|
||||
public struct ChatMessageBubbleContentTapAction {
|
||||
public struct Url {
|
||||
public var url: String
|
||||
public var concealed: Bool
|
||||
public var allowInlineWebpageResolution: Bool
|
||||
|
||||
public init(
|
||||
url: String,
|
||||
concealed: Bool,
|
||||
allowInlineWebpageResolution: Bool = false
|
||||
) {
|
||||
self.url = url
|
||||
self.concealed = concealed
|
||||
self.allowInlineWebpageResolution = allowInlineWebpageResolution
|
||||
}
|
||||
}
|
||||
|
||||
public enum Content {
|
||||
case none
|
||||
case url(Url)
|
||||
case textMention(String)
|
||||
case peerMention(peerId: PeerId, mention: String, openProfile: Bool)
|
||||
case botCommand(String)
|
||||
case hashtag(String?, String)
|
||||
case instantPage
|
||||
case wallpaper
|
||||
case theme
|
||||
case call(peerId: PeerId, isVideo: Bool)
|
||||
case openMessage
|
||||
case timecode(Double, String)
|
||||
case tooltip(String, ASDisplayNode?, CGRect?)
|
||||
case bankCard(String)
|
||||
case ignore
|
||||
case openPollResults(Data)
|
||||
case copy(String)
|
||||
case largeEmoji(String, String?, TelegramMediaFile)
|
||||
case customEmoji(TelegramMediaFile)
|
||||
}
|
||||
|
||||
public var content: Content
|
||||
public var activate: (() -> Promise<Bool>?)?
|
||||
|
||||
public init(content: Content, activate: (() -> Promise<Bool>?)? = nil) {
|
||||
self.content = content
|
||||
self.activate = activate
|
||||
}
|
||||
}
|
||||
|
||||
public final class ChatMessageBubbleContentItem {
|
||||
@ -235,7 +261,7 @@ open class ChatMessageBubbleContentNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
open func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
|
||||
open func updateTouchesAtPoint(_ point: CGPoint?) {
|
||||
|
||||
@ -521,6 +521,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
private let mainContainerNode: ContextControllerSourceNode
|
||||
private let backgroundWallpaperNode: ChatMessageBubbleBackdrop
|
||||
private let backgroundNode: ChatMessageBackground
|
||||
private var backgroundHighlightNode: ChatMessageBackground?
|
||||
private let shadowNode: ChatMessageShadowNode
|
||||
private var clippingNode: ChatMessageBubbleClippingNode
|
||||
|
||||
@ -557,7 +558,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
private let messageAccessibilityArea: AccessibilityAreaNode
|
||||
|
||||
private var backgroundType: ChatMessageBackgroundType?
|
||||
private var highlightedState: Bool = false
|
||||
|
||||
private struct HighlightedState: Equatable {
|
||||
var quote: String?
|
||||
}
|
||||
private var highlightedState: HighlightedState?
|
||||
|
||||
private var backgroundFrameTransition: (CGRect, CGRect)?
|
||||
|
||||
@ -703,7 +708,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
}
|
||||
|
||||
if let singleUrl = accessibilityData.singleUrl {
|
||||
strongSelf.item?.controllerInteraction.openUrl(singleUrl, false, false, strongSelf.item?.content.firstMessage, nil)
|
||||
strongSelf.item?.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: singleUrl, concealed: false, external: false, message: strongSelf.item?.content.firstMessage))
|
||||
return true
|
||||
}
|
||||
|
||||
@ -1007,7 +1012,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
for contentNode in strongSelf.contentNodes {
|
||||
let contentNodePoint = strongSelf.view.convert(point, to: contentNode.view)
|
||||
let tapAction = contentNode.tapActionAtPoint(contentNodePoint, gesture: .tap, isEstimating: true)
|
||||
switch tapAction {
|
||||
switch tapAction.content {
|
||||
case .none:
|
||||
break
|
||||
case .ignore:
|
||||
@ -1066,7 +1071,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
for contentNode in strongSelf.contentNodes {
|
||||
let contentNodePoint = strongSelf.view.convert(point, to: contentNode.view)
|
||||
let tapAction = contentNode.tapActionAtPoint(contentNodePoint, gesture: .tap, isEstimating: true)
|
||||
switch tapAction {
|
||||
switch tapAction.content {
|
||||
case .none:
|
||||
if let _ = strongSelf.item?.controllerInteraction.tapMessage {
|
||||
return .waitForSingleTap
|
||||
@ -2818,7 +2823,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
if item.presentationData.theme.theme.forceSync {
|
||||
legacyTransition = .immediate
|
||||
}
|
||||
strongSelf.backgroundNode.setType(type: backgroundType, highlighted: strongSelf.highlightedState, graphics: graphics, maskMode: strongSelf.backgroundMaskMode, hasWallpaper: hasWallpaper, transition: legacyTransition, backgroundNode: presentationContext.backgroundNode)
|
||||
strongSelf.backgroundNode.setType(type: backgroundType, highlighted: false, graphics: graphics, maskMode: strongSelf.backgroundMaskMode, hasWallpaper: hasWallpaper, transition: legacyTransition, backgroundNode: presentationContext.backgroundNode)
|
||||
strongSelf.backgroundWallpaperNode.setType(type: backgroundType, theme: item.presentationData.theme, essentialGraphics: graphics, maskMode: strongSelf.backgroundMaskMode, backgroundNode: presentationContext.backgroundNode)
|
||||
strongSelf.shadowNode.setType(type: backgroundType, hasWallpaper: hasWallpaper, graphics: graphics)
|
||||
|
||||
@ -3535,6 +3540,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
let backgroundAnimation = ListViewAnimation(from: strongSelf.backgroundNode.frame, to: backgroundFrame, duration: duration * UIView.animationDurationFactor(), curve: strongSelf.preferredAnimationCurve, beginAt: beginAt, update: { [weak strongSelf] _, frame in
|
||||
if let strongSelf = strongSelf {
|
||||
strongSelf.backgroundNode.frame = frame
|
||||
if let backgroundHighlightNode = strongSelf.backgroundHighlightNode {
|
||||
backgroundHighlightNode.frame = frame
|
||||
backgroundHighlightNode.updateLayout(size: frame.size, transition: .immediate)
|
||||
}
|
||||
strongSelf.clippingNode.position = CGPoint(x: frame.midX, y: frame.midY)
|
||||
strongSelf.clippingNode.bounds = CGRect(origin: CGPoint(x: frame.minX, y: frame.minY), size: frame.size)
|
||||
|
||||
@ -3546,6 +3555,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
strongSelf.setAnimationForKey("backgroundNodeFrame", animation: backgroundAnimation)
|
||||
} else {
|
||||
animation.animator.updateFrame(layer: strongSelf.backgroundNode.layer, frame: backgroundFrame, completion: nil)
|
||||
if let backgroundHighlightNode = strongSelf.backgroundHighlightNode {
|
||||
animation.animator.updateFrame(layer: backgroundHighlightNode.layer, frame: backgroundFrame, completion: nil)
|
||||
backgroundHighlightNode.updateLayout(size: backgroundFrame.size, transition: animation)
|
||||
}
|
||||
animation.animator.updatePosition(layer: strongSelf.clippingNode.layer, position: backgroundFrame.center, completion: nil)
|
||||
strongSelf.clippingNode.clipsToBounds = shouldClipOnTransitions
|
||||
animation.animator.updateBounds(layer: strongSelf.clippingNode.layer, bounds: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: backgroundFrame.size), completion: { [weak strongSelf] _ in
|
||||
@ -3608,6 +3621,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
|
||||
if case .System = animation, strongSelf.mainContextSourceNode.isExtractedToContextPreview {
|
||||
legacyTransition.updateFrame(node: strongSelf.backgroundNode, frame: backgroundFrame)
|
||||
if let backgroundHighlightNode = strongSelf.backgroundHighlightNode {
|
||||
legacyTransition.updateFrame(node: backgroundHighlightNode, frame: backgroundFrame, completion: nil)
|
||||
backgroundHighlightNode.updateLayout(size: backgroundFrame.size, transition: legacyTransition)
|
||||
}
|
||||
|
||||
legacyTransition.updateFrame(node: strongSelf.clippingNode, frame: backgroundFrame)
|
||||
legacyTransition.updateBounds(node: strongSelf.clippingNode, bounds: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: backgroundFrame.size))
|
||||
@ -3617,6 +3634,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
strongSelf.shadowNode.updateLayout(backgroundFrame: backgroundFrame, transition: legacyTransition)
|
||||
} else {
|
||||
strongSelf.backgroundNode.frame = backgroundFrame
|
||||
if let backgroundHighlightNode = strongSelf.backgroundHighlightNode {
|
||||
backgroundHighlightNode.frame = backgroundFrame
|
||||
backgroundHighlightNode.updateLayout(size: backgroundFrame.size, transition: .immediate)
|
||||
}
|
||||
|
||||
strongSelf.clippingNode.frame = backgroundFrame
|
||||
strongSelf.clippingNode.bounds = CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: backgroundFrame.size)
|
||||
strongSelf.backgroundNode.updateLayout(size: backgroundFrame.size, transition: .immediate)
|
||||
@ -3699,56 +3721,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
|
||||
override public func shouldAnimateHorizontalFrameTransition() -> Bool {
|
||||
return false
|
||||
/*if let _ = self.backgroundFrameTransition {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}*/
|
||||
}
|
||||
|
||||
override public func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) {
|
||||
super.animateFrameTransition(progress, currentValue)
|
||||
|
||||
/*if let backgroundFrameTransition = self.backgroundFrameTransition {
|
||||
let backgroundFrame = CGRect.interpolator()(backgroundFrameTransition.0, backgroundFrameTransition.1, progress) as! CGRect
|
||||
self.backgroundNode.frame = backgroundFrame
|
||||
|
||||
self.clippingNode.frame = backgroundFrame
|
||||
self.clippingNode.bounds = CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: backgroundFrame.size)
|
||||
|
||||
self.backgroundNode.updateLayout(size: backgroundFrame.size, transition: .immediate)
|
||||
self.backgroundWallpaperNode.frame = backgroundFrame
|
||||
self.shadowNode.updateLayout(backgroundFrame: backgroundFrame, transition: .immediate)
|
||||
|
||||
if let type = self.backgroundNode.type {
|
||||
var incomingOffset: CGFloat = 0.0
|
||||
switch type {
|
||||
case .incoming:
|
||||
incomingOffset = 5.0
|
||||
default:
|
||||
break
|
||||
}
|
||||
self.mainContextSourceNode.contentRect = backgroundFrame.offsetBy(dx: incomingOffset, dy: 0.0)
|
||||
self.mainContainerNode.targetNodeForActivationProgressContentRect = self.mainContextSourceNode.contentRect
|
||||
if !self.mainContextSourceNode.isExtractedToContextPreview {
|
||||
if let (rect, size) = self.absoluteRect {
|
||||
self.updateAbsoluteRect(rect, within: size)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.messageAccessibilityArea.frame = backgroundFrame
|
||||
|
||||
if let item = self.item, let shareButtonNode = self.shareButtonNode {
|
||||
let buttonSize = shareButtonNode.update(presentationData: item.presentationData, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: true)
|
||||
shareButtonNode.frame = CGRect(origin: CGPoint(x: backgroundFrame.maxX + 8.0, y: backgroundFrame.maxY - buttonSize.width - 1.0), size: buttonSize)
|
||||
}
|
||||
|
||||
if CGFloat(1.0).isLessThanOrEqualTo(progress) {
|
||||
self.backgroundFrameTransition = nil
|
||||
|
||||
self.clippingNode.clipsToBounds = false
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
@objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||
@ -3948,7 +3924,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
let convertedLocation = self.view.convert(location, to: contentNode.view)
|
||||
|
||||
let tapAction = contentNode.tapActionAtPoint(convertedLocation, gesture: gesture, isEstimating: false)
|
||||
switch tapAction {
|
||||
switch tapAction.content {
|
||||
case .none:
|
||||
if let item = self.item, self.backgroundNode.frame.contains(CGPoint(x: self.frame.width - location.x, y: location.y)), let tapMessage = self.item?.controllerInteraction.tapMessage {
|
||||
return .action({
|
||||
@ -3964,12 +3940,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
return .action({
|
||||
})
|
||||
}
|
||||
case let .url(url, concealed, activate):
|
||||
case let .url(url):
|
||||
return .action({ [weak self] in
|
||||
guard let self, let item = self.item else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.openUrl(url, concealed, nil, item.content.firstMessage, activate?())
|
||||
item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url.url, concealed: url.concealed, message: item.content.firstMessage, allowInlineWebpageResolution: url.allowInlineWebpageResolution, progress: tapAction.activate?()))
|
||||
})
|
||||
case let .peerMention(peerId, _, openProfile):
|
||||
return .action({ [weak self] in
|
||||
@ -4123,12 +4099,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
tapMessage = contentNode.item?.message
|
||||
}
|
||||
let tapAction = contentNode.tapActionAtPoint(convertedLocation, gesture: gesture, isEstimating: false)
|
||||
switch tapAction {
|
||||
switch tapAction.content {
|
||||
case .none, .ignore:
|
||||
break
|
||||
case let .url(url, _, _):
|
||||
case let .url(url):
|
||||
return .action({
|
||||
item.controllerInteraction.longTap(.url(url), message)
|
||||
item.controllerInteraction.longTap(.url(url.url), message)
|
||||
})
|
||||
case let .peerMention(peerId, mention, _):
|
||||
return .action({
|
||||
@ -4480,43 +4456,114 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
return
|
||||
}
|
||||
|
||||
var highlighted = false
|
||||
var highlightedQuote: String?
|
||||
var highlightedState: HighlightedState?
|
||||
|
||||
for contentNode in self.contentNodes {
|
||||
let _ = contentNode.updateHighlightedState(animated: animated)
|
||||
}
|
||||
|
||||
if let highlightedState = item.controllerInteraction.highlightedState {
|
||||
if let highlightedStateValue = item.controllerInteraction.highlightedState {
|
||||
for (message, _) in item.content {
|
||||
if highlightedState.messageStableId == message.stableId {
|
||||
highlighted = true
|
||||
highlightedQuote = highlightedState.quote
|
||||
if highlightedStateValue.messageStableId == message.stableId {
|
||||
highlightedState = HighlightedState(quote: highlightedStateValue.quote)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.highlightedState != highlighted {
|
||||
self.highlightedState = highlighted
|
||||
if self.highlightedState != highlightedState {
|
||||
self.highlightedState = highlightedState
|
||||
|
||||
for contentNode in self.contentNodes {
|
||||
if let contentNode = contentNode as? ChatMessageTextBubbleContentNode {
|
||||
contentNode.updateQuoteTextHighlightState(text: nil, color: .clear, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
if let backgroundType = self.backgroundType {
|
||||
let graphics = PresentationResourcesChat.principalGraphics(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners)
|
||||
|
||||
let hasWallpaper = item.presentationData.theme.wallpaper.hasWallpaper
|
||||
self.backgroundNode.setType(type: backgroundType, highlighted: highlighted, graphics: graphics, maskMode: self.backgroundMaskMode, hasWallpaper: hasWallpaper, transition: animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate, backgroundNode: item.controllerInteraction.presentationContext.backgroundNode)
|
||||
}
|
||||
}
|
||||
|
||||
if let highlightedQuote {
|
||||
for contentNode in self.contentNodes {
|
||||
if let contentNode = contentNode as? ChatMessageTextBubbleContentNode {
|
||||
contentNode.updateQuoteTextHighlightState(text: highlightedQuote, animated: animated)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for contentNode in self.contentNodes {
|
||||
if let contentNode = contentNode as? ChatMessageTextBubbleContentNode {
|
||||
contentNode.updateQuoteTextHighlightState(text: nil, animated: animated)
|
||||
if self.highlightedState != nil {
|
||||
let backgroundHighlightNode: ChatMessageBackground
|
||||
if let current = self.backgroundHighlightNode {
|
||||
backgroundHighlightNode = current
|
||||
} else {
|
||||
backgroundHighlightNode = ChatMessageBackground()
|
||||
self.mainContextSourceNode.contentNode.insertSubnode(backgroundHighlightNode, aboveSubnode: self.backgroundNode)
|
||||
self.backgroundHighlightNode = backgroundHighlightNode
|
||||
|
||||
backgroundHighlightNode.setType(type: backgroundType, highlighted: true, graphics: graphics, maskMode: true, hasWallpaper: false, transition: .immediate, backgroundNode: nil)
|
||||
|
||||
backgroundHighlightNode.frame = self.backgroundNode.frame
|
||||
backgroundHighlightNode.updateLayout(size: backgroundHighlightNode.frame.size, transition: .immediate)
|
||||
|
||||
if highlightedState?.quote != nil {
|
||||
Queue.mainQueue().after(0.3, { [weak self] in
|
||||
guard let self, let item = self.item, let backgroundHighlightNode = self.backgroundHighlightNode else {
|
||||
return
|
||||
}
|
||||
|
||||
if let highlightedState = self.highlightedState, let quote = highlightedState.quote {
|
||||
let hasWallpaper = item.presentationData.theme.wallpaper.hasWallpaper
|
||||
let incoming: PresentationThemeBubbleColorComponents = !hasWallpaper ? item.presentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper : item.presentationData.theme.theme.chat.message.incoming.bubble.withWallpaper
|
||||
let outgoing: PresentationThemeBubbleColorComponents = !hasWallpaper ? item.presentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper : item.presentationData.theme.theme.chat.message.outgoing.bubble.withWallpaper
|
||||
|
||||
let highlightColor: UIColor
|
||||
if item.message.effectivelyIncoming(item.context.account.peerId) {
|
||||
highlightColor = incoming.highlightedFill
|
||||
} else {
|
||||
highlightColor = outgoing.highlightedFill
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
|
||||
|
||||
var quoteFrame: CGRect?
|
||||
for contentNode in self.contentNodes {
|
||||
if let contentNode = contentNode as? ChatMessageTextBubbleContentNode {
|
||||
contentNode.updateQuoteTextHighlightState(text: quote, color: highlightColor, animated: false)
|
||||
var sourceFrame = backgroundHighlightNode.view.convert(backgroundHighlightNode.bounds, to: contentNode.view)
|
||||
if item.message.effectivelyIncoming(item.context.account.peerId) {
|
||||
sourceFrame.origin.x += 6.0
|
||||
sourceFrame.size.width -= 6.0
|
||||
} else {
|
||||
sourceFrame.size.width -= 6.0
|
||||
}
|
||||
|
||||
if let localFrame = contentNode.animateQuoteTextHighlightIn(sourceFrame: sourceFrame, transition: transition) {
|
||||
if self.contentNodes[0] !== contentNode && self.contentNodes[0].supernode === contentNode.supernode {
|
||||
contentNode.supernode?.insertSubnode(contentNode, belowSubnode: self.contentNodes[0])
|
||||
}
|
||||
|
||||
quoteFrame = contentNode.view.convert(localFrame, to: backgroundHighlightNode.view.superview)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let quoteFrame {
|
||||
self.backgroundHighlightNode = nil
|
||||
|
||||
backgroundHighlightNode.updateLayout(size: quoteFrame.size, transition: transition)
|
||||
transition.updateFrame(node: backgroundHighlightNode, frame: quoteFrame)
|
||||
backgroundHighlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, delay: 0.1, removeOnCompletion: false, completion: { [weak backgroundHighlightNode] _ in
|
||||
backgroundHighlightNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let backgroundHighlightNode = self.backgroundHighlightNode {
|
||||
self.backgroundHighlightNode = nil
|
||||
if animated {
|
||||
backgroundHighlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak backgroundHighlightNode] _ in
|
||||
backgroundHighlightNode?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
backgroundHighlightNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -262,7 +262,7 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
if self.buttonNode.frame.contains(point) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
} else if self.bounds.contains(point), let item = self.item {
|
||||
var isVideo = false
|
||||
for media in item.message.media {
|
||||
@ -270,9 +270,9 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
isVideo = isVideoValue
|
||||
}
|
||||
}
|
||||
return .call(peerId: item.message.id.peerId, isVideo: isVideo)
|
||||
return ChatMessageBubbleContentTapAction(content: .call(peerId: item.message.id.peerId, isVideo: isVideo))
|
||||
} else {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -410,9 +410,9 @@ public final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContent
|
||||
|
||||
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
if self.buttonNode.frame.contains(point) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
|
||||
@ -379,12 +379,12 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
if self.buttonNode.frame.contains(point) {
|
||||
return .openMessage
|
||||
return ChatMessageBubbleContentTapAction(content: .openMessage)
|
||||
}
|
||||
if self.dateAndStatusNode.supernode != nil, let _ = self.dateAndStatusNode.hitTest(self.view.convert(point, to: self.dateAndStatusNode.view), with: nil) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
|
||||
@objc private func contactTap(_ recognizer: UITapGestureRecognizer) {
|
||||
|
||||
@ -100,7 +100,7 @@ public final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessag
|
||||
}
|
||||
}*/
|
||||
}
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
|
||||
override public func updateHiddenMedia(_ media: [Media]?) -> Bool {
|
||||
|
||||
@ -95,7 +95,7 @@ public final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubble
|
||||
}
|
||||
}*/
|
||||
}
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
|
||||
override public func updateHiddenMedia(_ media: [Media]?) -> Bool {
|
||||
|
||||
@ -97,7 +97,7 @@ public final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBub
|
||||
let contentNodeFrame = self.contentNode.frame
|
||||
return self.contentNode.tapActionAtPoint(point.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY), gesture: gesture, isEstimating: isEstimating)
|
||||
}
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
|
||||
override public func updateTouchesAtPoint(_ point: CGPoint?) {
|
||||
|
||||
@ -221,10 +221,10 @@ public class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
if self.interactiveFileNode.dateAndStatusNode.supernode != nil, let _ = self.interactiveFileNode.dateAndStatusNode.hitTest(self.view.convert(point, to: self.interactiveFileNode.dateAndStatusNode.view), with: nil) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
if self.interactiveFileNode.hasTapAction(at: self.view.convert(point, to: self.interactiveFileNode.view)) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
return super.tapActionAtPoint(point, gesture: gesture, isEstimating: isEstimating)
|
||||
}
|
||||
|
||||
@ -129,7 +129,7 @@ public final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNod
|
||||
}
|
||||
}*/
|
||||
}
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
|
||||
override public func updateHiddenMedia(_ media: [Media]?) -> Bool {
|
||||
|
||||
@ -513,26 +513,26 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
if let (attributeText, fullText) = self.labelNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return .url(url: url, concealed: concealed, activate: nil)
|
||||
return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: concealed)))
|
||||
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
return .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)
|
||||
return ChatMessageBubbleContentTapAction(content: .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false))
|
||||
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
return .textMention(peerName)
|
||||
return ChatMessageBubbleContentTapAction(content: .textMention(peerName))
|
||||
} else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {
|
||||
return .botCommand(botCommand)
|
||||
return ChatMessageBubbleContentTapAction(content: .botCommand(botCommand))
|
||||
} else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
|
||||
return .hashtag(hashtag.peerName, hashtag.hashtag)
|
||||
return ChatMessageBubbleContentTapAction(content: .hashtag(hashtag.peerName, hashtag.hashtag))
|
||||
}
|
||||
}
|
||||
|
||||
if self.buttonNode.frame.contains(point) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
} else if let backgroundNode = self.backgroundNode, backgroundNode.frame.contains(point) {
|
||||
return .openMessage
|
||||
return ChatMessageBubbleContentTapAction(content: .openMessage)
|
||||
} else if self.mediaBackgroundNode.frame.contains(point) {
|
||||
return .openMessage
|
||||
return ChatMessageBubbleContentTapAction(content: .openMessage)
|
||||
} else {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -567,12 +567,12 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
||||
|
||||
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
if self.buttonNode.frame.contains(point) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
if self.dateAndStatusNode.supernode != nil, let _ = self.dateAndStatusNode.hitTest(self.view.convert(point, to: self.dateAndStatusNode.view), with: nil) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
|
||||
@ -414,18 +414,18 @@ public class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentN
|
||||
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
if !self.interactiveFileNode.isHidden {
|
||||
if self.interactiveFileNode.dateAndStatusNode.supernode != nil, let _ = self.interactiveFileNode.dateAndStatusNode.hitTest(self.view.convert(point, to: self.interactiveFileNode.dateAndStatusNode.view), with: nil) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
if self.interactiveFileNode.hasTapAction(at: self.view.convert(point, to: self.interactiveFileNode.view)) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
}
|
||||
if !self.interactiveVideoNode.isHidden {
|
||||
if self.interactiveVideoNode.dateAndStatusNode.supernode != nil, let _ = self.interactiveVideoNode.dateAndStatusNode.hitTest(self.view.convert(point, to: self.interactiveVideoNode.dateAndStatusNode.view), with: nil) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
if let audioTranscriptionButton = self.interactiveVideoNode.audioTranscriptionButton, let _ = audioTranscriptionButton.hitTest(self.view.convert(point, to: audioTranscriptionButton), with: nil) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
}
|
||||
return super.tapActionAtPoint(point, gesture: gesture, isEstimating: isEstimating)
|
||||
|
||||
@ -125,7 +125,7 @@ public final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContent
|
||||
}
|
||||
}*/
|
||||
}
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
|
||||
override public func updateHiddenMedia(_ media: [Media]?) -> Bool {
|
||||
|
||||
@ -753,7 +753,7 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
|
||||
if url.hasPrefix("tg://") {
|
||||
concealed = false
|
||||
}
|
||||
item.controllerInteraction.openUrl(url, concealed, nil, nil, nil)
|
||||
item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: concealed))
|
||||
case .requestMap:
|
||||
item.controllerInteraction.shareCurrentLocation()
|
||||
case .requestPhone:
|
||||
|
||||
@ -505,7 +505,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
|
||||
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
|
||||
@objc private func imageTap(_ recognizer: UITapGestureRecognizer) {
|
||||
|
||||
@ -455,7 +455,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
|
||||
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
|
||||
override public func animateInsertion(_ currentTimestamp: Double, duration: Double) {
|
||||
|
||||
@ -1608,17 +1608,17 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
if let (attributeText, fullText) = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return .url(url: url, concealed: concealed, activate: nil)
|
||||
return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: concealed)))
|
||||
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
return .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)
|
||||
return ChatMessageBubbleContentTapAction(content: .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false))
|
||||
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
return .textMention(peerName)
|
||||
return ChatMessageBubbleContentTapAction(content: .textMention(peerName))
|
||||
} else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {
|
||||
return .botCommand(botCommand)
|
||||
return ChatMessageBubbleContentTapAction(content: .botCommand(botCommand))
|
||||
} else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
|
||||
return .hashtag(hashtag.peerName, hashtag.hashtag)
|
||||
return ChatMessageBubbleContentTapAction(content: .hashtag(hashtag.peerName, hashtag.hashtag))
|
||||
} else {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
} else {
|
||||
var isBotChat: Bool = false
|
||||
@ -1629,7 +1629,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
for optionNode in self.optionNodes {
|
||||
if optionNode.frame.contains(point), case .tap = gesture {
|
||||
if optionNode.isUserInteractionEnabled {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
} else if let result = optionNode.currentResult, let item = self.item, !Namespaces.Message.allScheduled.contains(item.message.id.namespace), let poll = self.poll, let option = optionNode.option, !isBotChat {
|
||||
switch poll.publicity {
|
||||
case .anonymous:
|
||||
@ -1648,7 +1648,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
string = item.presentationData.strings.MessagePoll_QuizCount(result.count)
|
||||
}
|
||||
}
|
||||
return .tooltip(string, optionNode, optionNode.bounds.offsetBy(dx: 0.0, dy: 10.0))
|
||||
return ChatMessageBubbleContentTapAction(content: .tooltip(string, optionNode, optionNode.bounds.offsetBy(dx: 0.0, dy: 10.0)))
|
||||
case .public:
|
||||
var hasNonZeroVoters = false
|
||||
if let voters = poll.results.voters {
|
||||
@ -1661,24 +1661,24 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
if hasNonZeroVoters {
|
||||
if !isEstimating {
|
||||
return .openPollResults(option.opaqueIdentifier)
|
||||
return ChatMessageBubbleContentTapAction(content: .openPollResults(option.opaqueIdentifier))
|
||||
}
|
||||
return .openMessage
|
||||
return ChatMessageBubbleContentTapAction(content: .openMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.buttonNode.isUserInteractionEnabled, !self.buttonNode.isHidden, self.buttonNode.frame.contains(point) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
if self.avatarsNode.isUserInteractionEnabled, !self.avatarsNode.isHidden, self.avatarsNode.frame.contains(point) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
if self.solutionButtonNode.isUserInteractionEnabled, !self.solutionButtonNode.isHidden, !self.solutionButtonNode.alpha.isZero, self.solutionButtonNode.frame.contains(point) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -314,9 +314,9 @@ public class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleCont
|
||||
|
||||
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
if self.mediaBackgroundNode.frame.contains(point) {
|
||||
return .openMessage
|
||||
return ChatMessageBubbleContentTapAction(content: .openMessage)
|
||||
} else {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -557,9 +557,9 @@ public final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleConte
|
||||
|
||||
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
if let result = self.buttonsNode.hitTest(self.view.convert(point, to: self.buttonsNode.view), with: nil), result !== self.buttonsNode.view {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
|
||||
@ -26,6 +26,26 @@ private let quoteIcon: UIImage = {
|
||||
return UIImage(bundleImageName: "Chat/Message/ReplyQuoteIcon")!.precomposed().withRenderingMode(.alwaysTemplate)
|
||||
}()
|
||||
|
||||
private let channelIcon: UIImage = {
|
||||
let sourceImage = UIImage(bundleImageName: "Chat/Input/Accessory Panels/PanelTextChannelIcon")!
|
||||
return generateImage(CGSize(width: sourceImage.size.width + 4.0, height: sourceImage.size.height + 4.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
UIGraphicsPushContext(context)
|
||||
sourceImage.draw(at: CGPoint(x: 2.0, y: 2.0))
|
||||
UIGraphicsPopContext()
|
||||
})!.precomposed().withRenderingMode(.alwaysTemplate)
|
||||
}()
|
||||
|
||||
private let groupIcon: UIImage = {
|
||||
let sourceImage = UIImage(bundleImageName: "Chat/Input/Accessory Panels/PanelTextGroupIcon")!
|
||||
return generateImage(CGSize(width: sourceImage.size.width + 3.0, height: sourceImage.size.height + 4.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
UIGraphicsPushContext(context)
|
||||
sourceImage.draw(at: CGPoint(x: 3.0, y: 1.0))
|
||||
UIGraphicsPopContext()
|
||||
})!.precomposed().withRenderingMode(.alwaysTemplate)
|
||||
}()
|
||||
|
||||
public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
public final class TransitionReplyPanel {
|
||||
public let titleNode: ASDisplayNode
|
||||
@ -135,29 +155,119 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
let titleFont = Font.semibold(fontSize)
|
||||
let textFont = Font.regular(fontSize)
|
||||
|
||||
var titleString: String
|
||||
var titleString: NSAttributedString
|
||||
var textString: NSAttributedString
|
||||
let isMedia: Bool
|
||||
let isText: Bool
|
||||
var isExpiredStory: Bool = false
|
||||
var isStory: Bool = false
|
||||
|
||||
let titleColor: UIColor
|
||||
let mainColor: UIColor
|
||||
let dustColor: UIColor
|
||||
var secondaryColor: UIColor?
|
||||
|
||||
var authorNameColor: UIColor?
|
||||
var dashSecondaryColor: UIColor?
|
||||
|
||||
let author = arguments.message?.effectiveAuthor
|
||||
|
||||
authorNameColor = author?.nameColor?.color
|
||||
dashSecondaryColor = author?.nameColor?.dashColors.1
|
||||
|
||||
switch arguments.type {
|
||||
case let .bubble(incoming):
|
||||
titleColor = incoming ? (authorNameColor ?? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor) : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
if incoming {
|
||||
if let authorNameColor {
|
||||
mainColor = authorNameColor
|
||||
secondaryColor = dashSecondaryColor
|
||||
} else {
|
||||
mainColor = arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor
|
||||
}
|
||||
} else {
|
||||
mainColor = arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
}
|
||||
dustColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.secondaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
|
||||
case .standalone:
|
||||
let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper)
|
||||
titleColor = serviceColor.primaryText
|
||||
|
||||
mainColor = serviceMessageColorComponents(chatTheme: arguments.presentationData.theme.theme.chat, wallpaper: arguments.presentationData.theme.wallpaper).primaryText
|
||||
dustColor = titleColor
|
||||
}
|
||||
|
||||
if let message = arguments.message {
|
||||
let author = message.effectiveAuthor
|
||||
titleString = author.flatMap(EnginePeer.init)?.displayTitle(strings: arguments.strings, displayOrder: arguments.presentationData.nameDisplayOrder) ?? arguments.strings.User_DeletedAccount
|
||||
let rawTitleString = author.flatMap(EnginePeer.init)?.displayTitle(strings: arguments.strings, displayOrder: arguments.presentationData.nameDisplayOrder) ?? arguments.strings.User_DeletedAccount
|
||||
titleString = NSAttributedString(string: rawTitleString, font: titleFont, textColor: titleColor)
|
||||
|
||||
if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported) || arguments.parentMessage.forwardInfo != nil {
|
||||
if let author = forwardInfo.author {
|
||||
titleString = EnginePeer(author).displayTitle(strings: arguments.strings, displayOrder: arguments.presentationData.nameDisplayOrder)
|
||||
let rawTitleString = EnginePeer(author).displayTitle(strings: arguments.strings, displayOrder: arguments.presentationData.nameDisplayOrder)
|
||||
titleString = NSAttributedString(string: rawTitleString, font: titleFont, textColor: titleColor)
|
||||
} else if let authorSignature = forwardInfo.authorSignature {
|
||||
titleString = authorSignature
|
||||
let rawTitleString = authorSignature
|
||||
titleString = NSAttributedString(string: rawTitleString, font: titleFont, textColor: titleColor)
|
||||
}
|
||||
}
|
||||
|
||||
if message.id.peerId != arguments.parentMessage.id.peerId {
|
||||
//TODO:localize
|
||||
if let peer = message.peers[message.id.peerId], (peer is TelegramChannel || peer is TelegramGroup) {
|
||||
titleString += " in \(peer.debugDisplayTitle)"
|
||||
if message.id.peerId != arguments.parentMessage.id.peerId, let peer = message.peers[message.id.peerId], (peer is TelegramChannel || peer is TelegramGroup) {
|
||||
final class RunDelegateData {
|
||||
let ascent: CGFloat
|
||||
let descent: CGFloat
|
||||
let width: CGFloat
|
||||
|
||||
init(ascent: CGFloat, descent: CGFloat, width: CGFloat) {
|
||||
self.ascent = ascent
|
||||
self.descent = descent
|
||||
self.width = width
|
||||
}
|
||||
}
|
||||
let font = titleFont
|
||||
let runDelegateData = RunDelegateData(
|
||||
ascent: font.ascender,
|
||||
descent: font.descender,
|
||||
width: channelIcon.size.width
|
||||
)
|
||||
var callbacks = CTRunDelegateCallbacks(
|
||||
version: kCTRunDelegateCurrentVersion,
|
||||
dealloc: { dataRef in
|
||||
Unmanaged<RunDelegateData>.fromOpaque(dataRef).release()
|
||||
},
|
||||
getAscent: { dataRef in
|
||||
let data = Unmanaged<RunDelegateData>.fromOpaque(dataRef)
|
||||
return data.takeUnretainedValue().ascent
|
||||
},
|
||||
getDescent: { dataRef in
|
||||
let data = Unmanaged<RunDelegateData>.fromOpaque(dataRef)
|
||||
return data.takeUnretainedValue().descent
|
||||
},
|
||||
getWidth: { dataRef in
|
||||
let data = Unmanaged<RunDelegateData>.fromOpaque(dataRef)
|
||||
return data.takeUnretainedValue().width
|
||||
}
|
||||
)
|
||||
|
||||
if let runDelegate = CTRunDelegateCreate(&callbacks, Unmanaged.passRetained(runDelegateData).toOpaque()) {
|
||||
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||
let rawTitleString = NSMutableAttributedString(attributedString: titleString)
|
||||
rawTitleString.insert(NSAttributedString(string: ">", attributes: [
|
||||
.attachment: channelIcon,
|
||||
.foregroundColor: titleColor,
|
||||
NSAttributedString.Key(rawValue: kCTRunDelegateAttributeName as String): runDelegate
|
||||
]), at: 0)
|
||||
titleString = rawTitleString
|
||||
} else {
|
||||
let rawTitleString = NSMutableAttributedString(attributedString: titleString)
|
||||
rawTitleString.append(NSAttributedString(string: ">", attributes: [
|
||||
.attachment: groupIcon,
|
||||
.foregroundColor: titleColor,
|
||||
NSAttributedString.Key(rawValue: kCTRunDelegateAttributeName as String): runDelegate
|
||||
]))
|
||||
rawTitleString.append(NSAttributedString(string: peer.debugDisplayTitle, font: titleFont, textColor: titleColor))
|
||||
titleString = rawTitleString
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,9 +277,11 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
isText = isTextValue
|
||||
} else if let replyForward = arguments.replyForward {
|
||||
if let replyAuthorId = replyForward.peerId, let replyAuthor = arguments.parentMessage.peers[replyAuthorId] {
|
||||
titleString = EnginePeer(replyAuthor).displayTitle(strings: arguments.strings, displayOrder: arguments.presentationData.nameDisplayOrder)
|
||||
let rawTitleString = EnginePeer(replyAuthor).displayTitle(strings: arguments.strings, displayOrder: arguments.presentationData.nameDisplayOrder)
|
||||
titleString = NSAttributedString(string: rawTitleString, font: titleFont, textColor: titleColor)
|
||||
} else {
|
||||
titleString = replyForward.authorName ?? " "
|
||||
let rawTitleString = replyForward.authorName ?? " "
|
||||
titleString = NSAttributedString(string: rawTitleString, font: titleFont, textColor: titleColor)
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
@ -191,9 +303,11 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
isText = replyForward.quote?.text != nil && replyForward.quote?.text != ""
|
||||
} else if let story = arguments.story {
|
||||
if let authorPeer = arguments.parentMessage.peers[story.peerId] {
|
||||
titleString = EnginePeer(authorPeer).displayTitle(strings: arguments.strings, displayOrder: arguments.presentationData.nameDisplayOrder)
|
||||
let rawTitleString = EnginePeer(authorPeer).displayTitle(strings: arguments.strings, displayOrder: arguments.presentationData.nameDisplayOrder)
|
||||
titleString = NSAttributedString(string: rawTitleString, font: titleFont, textColor: titleColor)
|
||||
} else {
|
||||
titleString = arguments.strings.User_DeletedAccount
|
||||
let rawTitleString = arguments.strings.User_DeletedAccount
|
||||
titleString = NSAttributedString(string: rawTitleString, font: titleFont, textColor: titleColor)
|
||||
}
|
||||
isText = false
|
||||
|
||||
@ -221,65 +335,20 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
isMedia = true
|
||||
}
|
||||
} else {
|
||||
titleString = " "
|
||||
titleString = NSAttributedString(string: " ", font: titleFont, textColor: titleColor)
|
||||
textString = NSAttributedString(string: " ")
|
||||
isMedia = true
|
||||
isText = false
|
||||
}
|
||||
|
||||
let placeholderColor: UIColor = arguments.parentMessage.effectivelyIncoming(arguments.context.account.peerId) ? arguments.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : arguments.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor
|
||||
let isIncoming = arguments.parentMessage.effectivelyIncoming(arguments.context.account.peerId)
|
||||
|
||||
let placeholderColor: UIColor = isIncoming ? arguments.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : arguments.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor
|
||||
|
||||
let titleColor: UIColor
|
||||
let textColor: UIColor
|
||||
let dustColor: UIColor
|
||||
|
||||
var authorNameColor: UIColor?
|
||||
var dashSecondaryColor: UIColor?
|
||||
|
||||
let author = arguments.message?.effectiveAuthor
|
||||
|
||||
// if ([Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(arguments.parentMessage.id.peerId.namespace) && author?.id.namespace == Namespaces.Peer.CloudUser) {
|
||||
authorNameColor = author?.nameColor?.color
|
||||
dashSecondaryColor = author?.nameColor?.dashColors.1
|
||||
// if let rawAuthorNameColor = authorNameColor {
|
||||
// var dimColors = false
|
||||
// switch arguments.presentationData.theme.theme.name {
|
||||
// case .builtin(.nightAccent), .builtin(.night):
|
||||
// dimColors = true
|
||||
// default:
|
||||
// break
|
||||
// }
|
||||
// if dimColors {
|
||||
// var hue: CGFloat = 0.0
|
||||
// var saturation: CGFloat = 0.0
|
||||
// var brightness: CGFloat = 0.0
|
||||
// rawAuthorNameColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: nil)
|
||||
// authorNameColor = UIColor(hue: hue, saturation: saturation * 0.7, brightness: min(1.0, brightness * 1.2), alpha: 1.0)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
let mainColor: UIColor
|
||||
var secondaryColor: UIColor?
|
||||
|
||||
var isIncoming = false
|
||||
switch arguments.type {
|
||||
case let .bubble(incoming):
|
||||
titleColor = incoming ? (authorNameColor ?? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor) : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
if incoming {
|
||||
isIncoming = true
|
||||
if let authorNameColor {
|
||||
mainColor = authorNameColor
|
||||
secondaryColor = dashSecondaryColor
|
||||
} else {
|
||||
mainColor = arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor
|
||||
}
|
||||
} else {
|
||||
mainColor = arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
if let _ = dashSecondaryColor {
|
||||
secondaryColor = arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
}
|
||||
}
|
||||
if isExpiredStory || isStory {
|
||||
textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
} else if isMedia {
|
||||
@ -287,17 +356,10 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
} else {
|
||||
textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.primaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.primaryTextColor
|
||||
}
|
||||
dustColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.secondaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
|
||||
case .standalone:
|
||||
let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper)
|
||||
titleColor = serviceColor.primaryText
|
||||
|
||||
mainColor = serviceMessageColorComponents(chatTheme: arguments.presentationData.theme.theme.chat, wallpaper: arguments.presentationData.theme.wallpaper).primaryText
|
||||
textColor = titleColor
|
||||
dustColor = titleColor
|
||||
}
|
||||
|
||||
|
||||
let messageText: NSAttributedString
|
||||
if isText, let message = arguments.message {
|
||||
var text: String
|
||||
@ -453,7 +515,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleString, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: maxTitleNumberOfLines, truncationType: .end, constrainedSize: CGSize(width: contrainedTextSize.width - additionalTitleWidth, height: contrainedTextSize.height), alignment: .natural, cutout: nil, insets: textInsets))
|
||||
let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: maxTitleNumberOfLines, truncationType: .end, constrainedSize: CGSize(width: contrainedTextSize.width - additionalTitleWidth, height: contrainedTextSize.height), alignment: .natural, cutout: nil, insets: textInsets))
|
||||
if isExpiredStory || isStory {
|
||||
contrainedTextSize.width -= 26.0
|
||||
}
|
||||
@ -707,9 +769,9 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
isHighlighted = true
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: isHighlighted ? 0.1 : 0.2, curve: .easeInOut)
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: isHighlighted ? 0.3 : 0.2, curve: .easeInOut)
|
||||
let scale: CGFloat = isHighlighted ? ((self.bounds.width - 5.0) / self.bounds.width) : 1.0
|
||||
transition.updateSublayerTransformScale(node: self, scale: scale)
|
||||
transition.updateSublayerTransformScale(node: self, scale: scale, beginWithCurrentState: true)
|
||||
}
|
||||
|
||||
public func animateFromInputPanel(sourceReplyPanel: TransitionReplyPanel, unclippedTransitionNode: ASDisplayNode? = nil, localRect: CGRect, transition: CombinedTransition) -> CGPoint {
|
||||
|
||||
@ -361,9 +361,9 @@ public class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
if self.mediaBackgroundNode.frame.contains(point) {
|
||||
return .openMessage
|
||||
return ChatMessageBubbleContentTapAction(content: .openMessage)
|
||||
} else {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon",
|
||||
"//submodules/TelegramUI/Components/Chat/MessageQuoteComponent",
|
||||
"//submodules/TelegramUI/Components/TextLoadingEffect",
|
||||
"//submodules/TelegramUI/Components/ChatControllerInteraction",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@ -25,6 +25,7 @@ import ChatMessageBubbleContentNode
|
||||
import ShimmeringLinkNode
|
||||
import ChatMessageItemCommon
|
||||
import TextLoadingEffect
|
||||
import ChatControllerInteraction
|
||||
|
||||
private final class CachedChatMessageText {
|
||||
let text: String
|
||||
@ -72,7 +73,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
private var linkPreviewOptionsDisposable: Disposable?
|
||||
private var linkPreviewHighlightingNodes: [LinkHighlightingNode] = []
|
||||
|
||||
private var quoteHighlightingNodes: [LinkHighlightingNode] = []
|
||||
private var quoteHighlightingNode: LinkHighlightingNode?
|
||||
|
||||
private var linkProgressRange: NSRange?
|
||||
private var linkProgressView: TextLoadingEffectView?
|
||||
@ -113,7 +114,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
self.addSubnode(self.textAccessibilityOverlayNode)
|
||||
|
||||
self.textAccessibilityOverlayNode.openUrl = { [weak self] url in
|
||||
self?.item?.controllerInteraction.openUrl(url, false, false, nil, nil)
|
||||
self?.item?.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: false, external: false))
|
||||
}
|
||||
|
||||
self.statusNode.reactionSelected = { [weak self] value in
|
||||
@ -678,14 +679,14 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
if case .tap = gesture {
|
||||
} else {
|
||||
if let item = self.item, let subject = item.associatedData.subject, case .messageOptions = subject {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
}
|
||||
|
||||
let textNodeFrame = self.textNode.textNode.frame
|
||||
if let (index, attributes) = self.textNode.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !(self.dustNode?.isRevealed ?? true) {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
} else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
var concealed = true
|
||||
var urlRange: NSRange?
|
||||
@ -693,7 +694,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
urlRange = urlRangeValue
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return .url(url: url, concealed: concealed, activate: { [weak self] in
|
||||
return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: concealed)), activate: { [weak self] in
|
||||
guard let self else {
|
||||
return nil
|
||||
}
|
||||
@ -721,23 +722,23 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
return promise
|
||||
})
|
||||
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
return .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)
|
||||
return ChatMessageBubbleContentTapAction(content: .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false))
|
||||
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
return .textMention(peerName)
|
||||
return ChatMessageBubbleContentTapAction(content: .textMention(peerName))
|
||||
} else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {
|
||||
return .botCommand(botCommand)
|
||||
return ChatMessageBubbleContentTapAction(content: .botCommand(botCommand))
|
||||
} else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
|
||||
return .hashtag(hashtag.peerName, hashtag.hashtag)
|
||||
return ChatMessageBubbleContentTapAction(content: .hashtag(hashtag.peerName, hashtag.hashtag))
|
||||
} else if let timecode = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Timecode)] as? TelegramTimecode {
|
||||
return .timecode(timecode.time, timecode.text)
|
||||
return ChatMessageBubbleContentTapAction(content: .timecode(timecode.time, timecode.text))
|
||||
} else if let bankCard = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BankCard)] as? String {
|
||||
return .bankCard(bankCard)
|
||||
return ChatMessageBubbleContentTapAction(content: .bankCard(bankCard))
|
||||
} else if let pre = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Pre)] as? String {
|
||||
return .copy(pre)
|
||||
return ChatMessageBubbleContentTapAction(content: .copy(pre))
|
||||
} else if let code = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Code)] as? String {
|
||||
return .copy(code)
|
||||
return ChatMessageBubbleContentTapAction(content: .copy(code))
|
||||
} else if let emoji = attributes[NSAttributedString.Key(rawValue: ChatTextInputAttributes.customEmoji.rawValue)] as? ChatTextInputTextCustomEmojiAttribute, let file = emoji.file {
|
||||
return .customEmoji(file)
|
||||
return ChatMessageBubbleContentTapAction(content: .customEmoji(file))
|
||||
} else {
|
||||
if let item = self.item, item.message.text.count == 1, !item.presentationData.largeEmoji {
|
||||
let (emoji, fitz) = item.message.text.basicEmoji
|
||||
@ -749,19 +750,19 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
|
||||
if let emojiFile = emojiFile {
|
||||
return .largeEmoji(emoji, fitz, emojiFile)
|
||||
return ChatMessageBubbleContentTapAction(content: .largeEmoji(emoji, fitz, emojiFile))
|
||||
} else {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
} else {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let _ = self.statusNode.hitTest(self.view.convert(point, to: self.statusNode.view), with: nil) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
}
|
||||
|
||||
@ -941,43 +942,75 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateQuoteTextHighlightState(text: String?, animated: Bool) {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
public func animateQuoteTextHighlightIn(sourceFrame: CGRect, transition: ContainedViewLayoutTransition) -> CGRect? {
|
||||
if let quoteHighlightingNode = self.quoteHighlightingNode {
|
||||
var currentRect = CGRect()
|
||||
for rect in quoteHighlightingNode.rects {
|
||||
if currentRect.isEmpty {
|
||||
currentRect = rect
|
||||
} else {
|
||||
currentRect = currentRect.union(rect)
|
||||
}
|
||||
}
|
||||
if !currentRect.isEmpty {
|
||||
currentRect = currentRect.insetBy(dx: -quoteHighlightingNode.inset, dy: -quoteHighlightingNode.inset)
|
||||
let innerRect = currentRect.offsetBy(dx: quoteHighlightingNode.frame.minX, dy: quoteHighlightingNode.frame.minY)
|
||||
|
||||
quoteHighlightingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.08, delay: 0.1)
|
||||
|
||||
let fromScale = CGPoint(x: sourceFrame.width / innerRect.width, y: sourceFrame.height / innerRect.height)
|
||||
|
||||
var fromTransform = CATransform3DIdentity
|
||||
let fromOffset = CGPoint(x: sourceFrame.midX - innerRect.midX, y: sourceFrame.midY - innerRect.midY)
|
||||
|
||||
fromTransform = CATransform3DTranslate(fromTransform, fromOffset.x, fromOffset.y, 0.0)
|
||||
|
||||
fromTransform = CATransform3DTranslate(fromTransform, -quoteHighlightingNode.bounds.width * 0.5 + currentRect.midX, -quoteHighlightingNode.bounds.height * 0.5 + currentRect.midY, 0.0)
|
||||
fromTransform = CATransform3DScale(fromTransform, fromScale.x, fromScale.y, 1.0)
|
||||
fromTransform = CATransform3DTranslate(fromTransform, quoteHighlightingNode.bounds.width * 0.5 - currentRect.midX, quoteHighlightingNode.bounds.height * 0.5 - currentRect.midY, 0.0)
|
||||
|
||||
quoteHighlightingNode.transform = fromTransform
|
||||
transition.updateTransform(node: quoteHighlightingNode, transform: CGAffineTransformIdentity)
|
||||
|
||||
return currentRect.offsetBy(dx: quoteHighlightingNode.frame.minX, dy: quoteHighlightingNode.frame.minY)
|
||||
}
|
||||
}
|
||||
var rectsSet: [[CGRect]] = []
|
||||
return nil
|
||||
}
|
||||
|
||||
public func updateQuoteTextHighlightState(text: String?, color: UIColor, animated: Bool) {
|
||||
var rectsSet: [CGRect] = []
|
||||
if let text = text, !text.isEmpty, let cachedLayout = self.textNode.textNode.cachedLayout, let string = cachedLayout.attributedString?.string {
|
||||
let nsString = string as NSString
|
||||
let range = nsString.range(of: text)
|
||||
if range.location != NSNotFound {
|
||||
if let rects = cachedLayout.rangeRects(in: range)?.rects, !rects.isEmpty {
|
||||
rectsSet = [rects]
|
||||
rectsSet = rects
|
||||
}
|
||||
}
|
||||
}
|
||||
for i in 0 ..< rectsSet.count {
|
||||
let rects = rectsSet[i]
|
||||
if !rectsSet.isEmpty {
|
||||
let rects = rectsSet
|
||||
let textHighlightNode: LinkHighlightingNode
|
||||
if i < self.quoteHighlightingNodes.count {
|
||||
textHighlightNode = self.quoteHighlightingNodes[i]
|
||||
if let current = self.quoteHighlightingNode {
|
||||
textHighlightNode = current
|
||||
} else {
|
||||
textHighlightNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor)
|
||||
self.quoteHighlightingNodes.append(textHighlightNode)
|
||||
textHighlightNode = LinkHighlightingNode(color: color)
|
||||
self.quoteHighlightingNode = textHighlightNode
|
||||
self.insertSubnode(textHighlightNode, belowSubnode: self.textNode.textNode)
|
||||
}
|
||||
textHighlightNode.frame = self.textNode.textNode.frame
|
||||
textHighlightNode.updateRects(rects)
|
||||
}
|
||||
for i in (rectsSet.count ..< self.quoteHighlightingNodes.count).reversed() {
|
||||
let node = self.quoteHighlightingNodes[i]
|
||||
self.quoteHighlightingNodes.remove(at: i)
|
||||
|
||||
if animated {
|
||||
node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in
|
||||
node?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
node.removeFromSupernode()
|
||||
} else {
|
||||
if let quoteHighlightingNode = self.quoteHighlightingNode {
|
||||
self.quoteHighlightingNode = nil
|
||||
if animated {
|
||||
quoteHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak quoteHighlightingNode] _ in
|
||||
quoteHighlightingNode?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
quoteHighlightingNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,11 +94,11 @@ public final class ChatMessageUnsupportedBubbleContentNode: ChatMessageBubbleCon
|
||||
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
if self.bounds.contains(point) {
|
||||
if self.buttonNode.frame.contains(point) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
} else {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
}
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
}
|
||||
|
||||
@ -461,11 +461,11 @@ public class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode
|
||||
|
||||
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
if self.statusOverlayNode.alpha > 0.0 {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
} else if self.mediaBackgroundNode.frame.contains(point) {
|
||||
return .openMessage
|
||||
return ChatMessageBubbleContentTapAction(content: .openMessage)
|
||||
} else {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatHistoryEntry",
|
||||
"//submodules/TelegramUI/Components/ChatControllerInteraction",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@ -18,6 +18,7 @@ import ChatMessageItemCommon
|
||||
import WallpaperPreviewMedia
|
||||
import ChatMessageInteractiveMediaNode
|
||||
import ChatMessageAttachedContentNode
|
||||
import ChatControllerInteraction
|
||||
|
||||
private let titleFont: UIFont = Font.semibold(15.0)
|
||||
|
||||
@ -52,6 +53,15 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
||||
} else if content.type == "telegram_theme" {
|
||||
item.controllerInteraction.openTheme(item.message)
|
||||
return
|
||||
} else {
|
||||
if content.title != nil || content.text != nil {
|
||||
var isConcealed = true
|
||||
if item.message.text.contains(content.url) {
|
||||
isConcealed = false
|
||||
}
|
||||
item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: content.url, concealed: isConcealed))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
let openChatMessageMode: ChatControllerInteractionOpenMessageMode
|
||||
@ -90,7 +100,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
||||
if item.message.text.contains(webpage.url) {
|
||||
isConcealed = false
|
||||
}
|
||||
item.controllerInteraction.openUrl(webpage.url, isConcealed, nil, nil, nil)
|
||||
item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: webpage.url, concealed: isConcealed))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -103,13 +113,13 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
||||
}
|
||||
self.contentNode.defaultContentAction = { [weak self] in
|
||||
guard let self, let item = self.item, let webPage = self.webPage, case let .Loaded(content) = webPage.content else {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
var isConcealed = true
|
||||
if item.message.text.contains(content.url) {
|
||||
isConcealed = false
|
||||
}
|
||||
return .url(url: content.url, concealed: isConcealed, activate: nil)
|
||||
return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: content.url, concealed: isConcealed, allowInlineWebpageResolution: true)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -501,22 +511,22 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
||||
|
||||
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
guard let item = self.item else {
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
if self.bounds.contains(point) {
|
||||
let contentNodeFrame = self.contentNode.frame
|
||||
let result = self.contentNode.tapActionAtPoint(point.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY), gesture: gesture, isEstimating: isEstimating)
|
||||
|
||||
if item.message.adAttribute != nil {
|
||||
if case .none = result {
|
||||
if case .none = result.content {
|
||||
if self.contentNode.hasActionAtPoint(point.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY)) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
switch result {
|
||||
switch result.content {
|
||||
case .none:
|
||||
break
|
||||
case let .textMention(value):
|
||||
@ -527,9 +537,9 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
||||
}
|
||||
switch websiteType(of: content.websiteName) {
|
||||
case .twitter:
|
||||
return .url(url: "https://twitter.com/\(mention)", concealed: false, activate: nil)
|
||||
return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: "https://twitter.com/\(mention)", concealed: false)))
|
||||
case .instagram:
|
||||
return .url(url: "https://instagram.com/\(mention)", concealed: false, activate: nil)
|
||||
return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: "https://instagram.com/\(mention)", concealed: false)))
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -542,9 +552,9 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
||||
}
|
||||
switch websiteType(of: content.websiteName) {
|
||||
case .twitter:
|
||||
return .url(url: "https://twitter.com/hashtag/\(hashtag)", concealed: false, activate: nil)
|
||||
return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: "https://twitter.com/hashtag/\(hashtag)", concealed: false)))
|
||||
case .instagram:
|
||||
return .url(url: "https://instagram.com/explore/tags/\(hashtag)", concealed: false, activate: nil)
|
||||
return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: "https://instagram.com/explore/tags/\(hashtag)", concealed: false)))
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -557,22 +567,22 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
||||
if content.instantPage != nil {
|
||||
switch websiteType(of: content.websiteName) {
|
||||
case .instagram, .twitter:
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
default:
|
||||
return .instantPage
|
||||
return ChatMessageBubbleContentTapAction(content: .instantPage)
|
||||
}
|
||||
} else if content.type == "telegram_background" {
|
||||
return .wallpaper
|
||||
return ChatMessageBubbleContentTapAction(content: .wallpaper)
|
||||
} else if content.type == "telegram_theme" {
|
||||
return .theme
|
||||
return ChatMessageBubbleContentTapAction(content: .theme)
|
||||
}
|
||||
}
|
||||
if self.contentNode.hasActionAtPoint(point.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY)) {
|
||||
return .ignore
|
||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||
}
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
return .none
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
}
|
||||
|
||||
override public func updateHiddenMedia(_ media: [Media]?) -> Bool {
|
||||
|
||||
@ -276,8 +276,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}, openMessageContextActions: { _, _, _, _ in
|
||||
}, navigateToMessage: { _, _, _ in }, navigateToMessageStandalone: { _ in
|
||||
}, navigateToThreadMessage: { _, _, _ in
|
||||
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { [weak self] url, _, _, _, _ in
|
||||
self?.openUrl(url)
|
||||
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { [weak self] url in
|
||||
self?.openUrl(url.url)
|
||||
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
|
||||
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() {
|
||||
strongSelf.context.sharedContext.openChatInstantPage(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType, navigationController: navigationController)
|
||||
|
||||
@ -19,6 +19,7 @@ import TextNodeWithEntities
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import AccessoryPanelNode
|
||||
import AppBundle
|
||||
|
||||
func textStringForForwardedMessage(_ message: Message, strings: PresentationStrings) -> (text: String, entities: [MessageTextEntity], isMedia: Bool) {
|
||||
for media in message.media {
|
||||
@ -91,7 +92,7 @@ public final class ForwardAccessoryPanelNode: AccessoryPanelNode {
|
||||
|
||||
let closeButton: HighlightableButtonNode
|
||||
let lineNode: ASImageNode
|
||||
let iconNode: ASImageNode
|
||||
let iconView: UIImageView
|
||||
let titleNode: ImmediateTextNode
|
||||
let textNode: ImmediateTextNodeWithEntities
|
||||
private var originalText: NSAttributedString?
|
||||
@ -127,10 +128,9 @@ public final class ForwardAccessoryPanelNode: AccessoryPanelNode {
|
||||
self.lineNode.displaysAsynchronously = false
|
||||
self.lineNode.image = PresentationResourcesChat.chatInputPanelVerticalSeparatorLineImage(theme)
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.displayWithoutProcessing = false
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
self.iconNode.image = PresentationResourcesChat.chatInputPanelForwardIconImage(theme)
|
||||
self.iconView = UIImageView()
|
||||
self.iconView.image = UIImage(bundleImageName: "Chat/Input/Accessory Panels/ForwardSettingsIcon")?.withRenderingMode(.alwaysTemplate)
|
||||
self.iconView.tintColor = theme.chat.inputPanel.panelControlAccentColor
|
||||
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.titleNode.maximumNumberOfLines = 1
|
||||
@ -148,7 +148,7 @@ public final class ForwardAccessoryPanelNode: AccessoryPanelNode {
|
||||
self.addSubnode(self.closeButton)
|
||||
|
||||
self.addSubnode(self.lineNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
self.view.addSubview(self.iconView)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.actionArea)
|
||||
@ -283,11 +283,11 @@ public final class ForwardAccessoryPanelNode: AccessoryPanelNode {
|
||||
}
|
||||
|
||||
override public func animateIn() {
|
||||
self.iconNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2)
|
||||
self.iconView.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
override public func animateOut() {
|
||||
self.iconNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
|
||||
self.iconView.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
override public func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
||||
@ -302,7 +302,7 @@ public final class ForwardAccessoryPanelNode: AccessoryPanelNode {
|
||||
|
||||
self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(theme), for: [])
|
||||
self.lineNode.image = PresentationResourcesChat.chatInputPanelVerticalSeparatorLineImage(theme)
|
||||
self.iconNode.image = PresentationResourcesChat.chatInputPanelForwardIconImage(theme)
|
||||
self.iconView.tintColor = theme.chat.inputPanel.panelControlAccentColor
|
||||
|
||||
let filteredMessages = self.messages
|
||||
|
||||
@ -350,8 +350,8 @@ public final class ForwardAccessoryPanelNode: AccessoryPanelNode {
|
||||
|
||||
self.lineNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 8.0), size: CGSize(width: 2.0, height: bounds.size.height - 10.0))
|
||||
|
||||
if let icon = self.iconNode.image {
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: 7.0 + inset, y: 10.0), size: icon.size)
|
||||
if let icon = self.iconView.image {
|
||||
self.iconView.frame = CGRect(origin: CGPoint(x: 7.0 + inset, y: 10.0), size: icon.size)
|
||||
}
|
||||
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: bounds.size.width - leftInset - textLineInset - rightInset - textRightInset, height: bounds.size.height))
|
||||
|
||||
@ -27,6 +27,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/AnimationCache",
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer",
|
||||
"//submodules/TelegramUI/Components/Chat/AccessoryPanelNode",
|
||||
"//submodules/TelegramUI/Components/CompositeTextNode",
|
||||
"//submodules/TelegramNotices",
|
||||
],
|
||||
visibility = [
|
||||
|
||||
@ -18,6 +18,8 @@ import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import AccessoryPanelNode
|
||||
import TelegramNotices
|
||||
import AppBundle
|
||||
import CompositeTextNode
|
||||
|
||||
public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
private let messageDisposable = MetaDisposable()
|
||||
@ -29,8 +31,8 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
|
||||
public let closeButton: HighlightableButtonNode
|
||||
public let lineNode: ASImageNode
|
||||
public let iconNode: ASImageNode
|
||||
public let titleNode: ImmediateTextNode
|
||||
public let iconView: UIImageView
|
||||
public let titleNode: CompositeTextNode
|
||||
public let textNode: ImmediateTextNodeWithEntities
|
||||
public let imageNode: TransformImageNode
|
||||
|
||||
@ -64,15 +66,16 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
self.lineNode.displaysAsynchronously = false
|
||||
self.lineNode.image = PresentationResourcesChat.chatInputPanelVerticalSeparatorLineImage(theme)
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.displayWithoutProcessing = false
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
self.iconNode.image = PresentationResourcesChat.chatInputPanelReplyIconImage(theme)
|
||||
self.iconView = UIImageView()
|
||||
if quote != nil {
|
||||
self.iconView.image = UIImage(bundleImageName: "Chat/Input/Accessory Panels/ReplyQuoteIcon")?.withRenderingMode(.alwaysTemplate)
|
||||
} else {
|
||||
self.iconView.image = UIImage(bundleImageName: "Chat/Input/Accessory Panels/ReplySettingsIcon")?.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
self.iconView.tintColor = theme.chat.inputPanel.panelControlAccentColor
|
||||
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.titleNode.maximumNumberOfLines = 1
|
||||
self.titleNode.displaysAsynchronously = false
|
||||
self.titleNode.insets = UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0)
|
||||
self.titleNode = CompositeTextNode()
|
||||
self.titleNode.imageTintColor = theme.chat.inputPanel.panelControlAccentColor
|
||||
|
||||
self.textNode = ImmediateTextNodeWithEntities()
|
||||
self.textNode.maximumNumberOfLines = 1
|
||||
@ -103,7 +106,7 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
self.addSubnode(self.closeButton)
|
||||
|
||||
self.addSubnode(self.lineNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
self.view.addSubview(self.iconView)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.imageNode)
|
||||
@ -114,7 +117,7 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
if let strongSelf = self {
|
||||
if messageView.message == nil {
|
||||
Queue.mainQueue().justDispatch {
|
||||
strongSelf.interfaceInteraction?.setupReplyMessage(nil, { _ in })
|
||||
strongSelf.interfaceInteraction?.setupReplyMessage(nil, { _, _ in })
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -235,27 +238,61 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
}
|
||||
}
|
||||
|
||||
var titleText: String
|
||||
if let quote = strongSelf.quote {
|
||||
//TODO:localize
|
||||
titleText = "Reply to quote by \(authorName)"
|
||||
var titleText: [CompositeTextNode.Component] = []
|
||||
if let peer = message?.peers[strongSelf.messageId.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
||||
let icon: UIImage?
|
||||
icon = UIImage(bundleImageName: "Chat/Input/Accessory Panels/PanelTextChannelIcon")?.withRenderingMode(.alwaysTemplate)
|
||||
|
||||
if let _ = strongSelf.quote {
|
||||
if let icon {
|
||||
let string = "Reply to Quote by "
|
||||
titleText = [.text(NSAttributedString(string: string, font: Font.medium(15.0), textColor: .white))]
|
||||
titleText.append(.icon(icon))
|
||||
titleText.append(.text(NSAttributedString(string: peer.debugDisplayTitle, font: Font.medium(15.0), textColor: .white)))
|
||||
}
|
||||
} else {
|
||||
if let icon {
|
||||
let string = "Reply to "
|
||||
titleText = [.text(NSAttributedString(string: string, font: Font.medium(15.0), textColor: .white))]
|
||||
titleText.append(.icon(icon))
|
||||
titleText.append(.text(NSAttributedString(string: peer.debugDisplayTitle, font: Font.medium(15.0), textColor: .white)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let _ = strongSelf.quote {
|
||||
//TODO:localize
|
||||
let string = "Reply to Quote by \(authorName)"
|
||||
titleText = [.text(NSAttributedString(string: string, font: Font.medium(15.0), textColor: .white))]
|
||||
} else {
|
||||
let string = strongSelf.strings.Conversation_ReplyMessagePanelTitle(authorName).string
|
||||
titleText = [.text(NSAttributedString(string: string, font: Font.medium(15.0), textColor: .white))]
|
||||
strongSelf.textNode.attributedText = messageText
|
||||
}
|
||||
|
||||
if strongSelf.messageId.peerId != strongSelf.chatPeerId {
|
||||
if let peer = message?.peers[strongSelf.messageId.peerId], (peer is TelegramChannel || peer is TelegramGroup) {
|
||||
let icon: UIImage?
|
||||
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
icon = UIImage(bundleImageName: "Chat/Input/Accessory Panels/PanelTextChannelIcon")?.withRenderingMode(.alwaysTemplate)
|
||||
} else {
|
||||
icon = UIImage(bundleImageName: "Chat/Input/Accessory Panels/PanelTextGroupIcon")?.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
if let icon {
|
||||
titleText.append(.icon(icon))
|
||||
titleText.append(.text(NSAttributedString(string: peer.debugDisplayTitle, font: Font.medium(15.0), textColor: .white)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let quote = strongSelf.quote {
|
||||
let textColor = strongSelf.theme.chat.inputPanel.primaryTextColor
|
||||
let quoteText = stringWithAppliedEntities(trimToLineCount(quote.text, lineCount: 1), entities: quote.entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message)
|
||||
|
||||
strongSelf.textNode.attributedText = quoteText
|
||||
} else {
|
||||
titleText = strongSelf.strings.Conversation_ReplyMessagePanelTitle(authorName).string
|
||||
strongSelf.textNode.attributedText = messageText
|
||||
}
|
||||
|
||||
if strongSelf.messageId.peerId != strongSelf.chatPeerId {
|
||||
if let peer = message?.peers[strongSelf.messageId.peerId], (peer is TelegramChannel || peer is TelegramGroup) {
|
||||
titleText += " in \(peer.debugDisplayTitle)"
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.titleNode.attributedText = NSAttributedString(string: titleText, font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor)
|
||||
strongSelf.titleNode.components = titleText
|
||||
|
||||
let headerString: String
|
||||
if let message = message, message.flags.contains(.Incoming), let author = message.author {
|
||||
@ -329,11 +366,11 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
}
|
||||
|
||||
override public func animateIn() {
|
||||
self.iconNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2)
|
||||
self.iconView.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
override public func animateOut() {
|
||||
self.iconNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
|
||||
self.iconView.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
override public func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
||||
@ -347,11 +384,9 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(theme), for: [])
|
||||
|
||||
self.lineNode.image = PresentationResourcesChat.chatInputPanelVerticalSeparatorLineImage(theme)
|
||||
self.iconNode.image = PresentationResourcesChat.chatInputPanelReplyIconImage(theme)
|
||||
self.iconView.tintColor = theme.chat.inputPanel.panelControlAccentColor
|
||||
|
||||
if let text = self.titleNode.attributedText?.string {
|
||||
self.titleNode.attributedText = NSAttributedString(string: text, font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor)
|
||||
}
|
||||
self.titleNode.imageTintColor = theme.chat.inputPanel.panelControlAccentColor
|
||||
|
||||
if let text = self.textNode.attributedText?.string {
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: self.textIsOptions ? self.theme.chat.inputPanel.secondaryTextColor : self.theme.chat.inputPanel.primaryTextColor)
|
||||
@ -387,8 +422,8 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
self.lineNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 8.0), size: CGSize(width: 2.0, height: bounds.size.height - 10.0))
|
||||
}
|
||||
|
||||
if let icon = self.iconNode.image {
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: 7.0 + inset, y: 10.0), size: icon.size)
|
||||
if let icon = self.iconView.image {
|
||||
self.iconView.frame = CGRect(origin: CGPoint(x: 7.0 + inset, y: 10.0), size: icon.size)
|
||||
}
|
||||
|
||||
var imageTextInset: CGFloat = 0.0
|
||||
@ -399,9 +434,9 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(x: leftInset + 9.0, y: 8.0), size: CGSize(width: 35.0, height: 35.0))
|
||||
}
|
||||
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: bounds.size.width - leftInset - textLineInset - rightInset - textRightInset - imageTextInset, height: bounds.size.height))
|
||||
let titleSize = self.titleNode.update(constrainedSize: CGSize(width: bounds.size.width - leftInset - textLineInset - rightInset - textRightInset - imageTextInset, height: bounds.size.height))
|
||||
if self.titleNode.supernode == self {
|
||||
self.titleNode.frame = CGRect(origin: CGPoint(x: leftInset + textLineInset + imageTextInset - self.titleNode.insets.left, y: 7.0 - self.titleNode.insets.top), size: titleSize)
|
||||
self.titleNode.frame = CGRect(origin: CGPoint(x: leftInset + textLineInset + imageTextInset, y: 7.0), size: titleSize)
|
||||
}
|
||||
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: bounds.size.width - leftInset - textLineInset - rightInset - textRightInset - imageTextInset, height: bounds.size.height))
|
||||
|
||||
@ -90,6 +90,24 @@ public final class ChatControllerInteraction {
|
||||
case groupParticipant(storyStats: PeerStoryStats?, avatarHeaderNode: ASDisplayNode?)
|
||||
}
|
||||
|
||||
public struct OpenUrl {
|
||||
public var url: String
|
||||
public var concealed: Bool
|
||||
public var external: Bool?
|
||||
public var message: Message?
|
||||
public var allowInlineWebpageResolution: Bool
|
||||
public var progress: Promise<Bool>?
|
||||
|
||||
public init(url: String, concealed: Bool, external: Bool? = nil, message: Message? = nil, allowInlineWebpageResolution: Bool = false, progress: Promise<Bool>? = nil) {
|
||||
self.url = url
|
||||
self.concealed = concealed
|
||||
self.external = external
|
||||
self.message = message
|
||||
self.allowInlineWebpageResolution = allowInlineWebpageResolution
|
||||
self.progress = progress
|
||||
}
|
||||
}
|
||||
|
||||
public let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool
|
||||
public let openPeer: (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void
|
||||
public let openPeerMention: (String) -> Void
|
||||
@ -113,7 +131,7 @@ public final class ChatControllerInteraction {
|
||||
public let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool, Bool) -> Void
|
||||
public let requestMessageActionUrlAuth: (String, MessageActionUrlSubject) -> Void
|
||||
public let activateSwitchInline: (PeerId?, String, ReplyMarkupButtonAction.PeerTypes?) -> Void
|
||||
public let openUrl: (String, Bool, Bool?, Message?, Promise<Bool>?) -> Void
|
||||
public let openUrl: (OpenUrl) -> Void
|
||||
public let shareCurrentLocation: () -> Void
|
||||
public let shareAccountContact: () -> Void
|
||||
public let sendBotCommand: (MessageId?, String) -> Void
|
||||
@ -227,7 +245,7 @@ public final class ChatControllerInteraction {
|
||||
requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void,
|
||||
requestMessageActionUrlAuth: @escaping (String, MessageActionUrlSubject) -> Void,
|
||||
activateSwitchInline: @escaping (PeerId?, String, ReplyMarkupButtonAction.PeerTypes?) -> Void,
|
||||
openUrl: @escaping (String, Bool, Bool?, Message?, Promise<Bool>?) -> Void,
|
||||
openUrl: @escaping (OpenUrl) -> Void,
|
||||
shareCurrentLocation: @escaping () -> Void,
|
||||
shareAccountContact: @escaping () -> Void,
|
||||
sendBotCommand: @escaping (MessageId?, String) -> Void,
|
||||
|
||||
19
submodules/TelegramUI/Components/CompositeTextNode/BUILD
Normal file
19
submodules/TelegramUI/Components/CompositeTextNode/BUILD
Normal file
@ -0,0 +1,19 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "CompositeTextNode",
|
||||
module_name = "CompositeTextNode",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display",
|
||||
"//submodules/AsyncDisplayKit",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@ -0,0 +1,117 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
|
||||
public class CompositeTextNode: ASDisplayNode {
|
||||
public enum Component: Equatable {
|
||||
case text(NSAttributedString)
|
||||
case icon(UIImage)
|
||||
}
|
||||
|
||||
public var components: [Component] = []
|
||||
|
||||
private var textNodes: [Int: ImmediateTextNode] = [:]
|
||||
private var iconViews: [Int: UIImageView] = [:]
|
||||
|
||||
public var imageTintColor: UIColor? {
|
||||
didSet {
|
||||
for (_, textNode) in self.textNodes {
|
||||
textNode.layer.layerTintColor = self.imageTintColor?.cgColor
|
||||
}
|
||||
for (_, iconView) in self.iconViews {
|
||||
iconView.tintColor = self.imageTintColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func update(constrainedSize: CGSize) -> CGSize {
|
||||
var validTextIds: [Int] = []
|
||||
var validIconIds: [Int] = []
|
||||
|
||||
var size = CGSize()
|
||||
|
||||
var nextTextId = 0
|
||||
var nextIconId = 0
|
||||
for component in self.components {
|
||||
switch component {
|
||||
case let .text(text):
|
||||
let id = nextTextId
|
||||
nextTextId += 1
|
||||
validTextIds.append(id)
|
||||
|
||||
let textNode: ImmediateTextNode
|
||||
if let current = self.textNodes[id] {
|
||||
textNode = current
|
||||
} else {
|
||||
textNode = ImmediateTextNode()
|
||||
textNode.maximumNumberOfLines = 1
|
||||
textNode.insets = UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0)
|
||||
textNode.layer.layerTintColor = self.imageTintColor?.cgColor
|
||||
self.textNodes[id] = textNode
|
||||
self.addSubnode(textNode)
|
||||
}
|
||||
textNode.attributedText = text
|
||||
|
||||
let textSize = textNode.updateLayout(CGSize(width: max(1.0, constrainedSize.width - size.width), height: constrainedSize.height))
|
||||
|
||||
textNode.frame = CGRect(origin: CGPoint(x: size.width - textNode.insets.left, y: -textNode.insets.top), size: textSize)
|
||||
size.width += textSize.width - textNode.insets.left - textNode.insets.right
|
||||
size.height = max(size.height, textSize.height - textNode.insets.top - textNode.insets.bottom)
|
||||
case let .icon(icon):
|
||||
let id = nextIconId
|
||||
nextIconId += 1
|
||||
validIconIds.append(id)
|
||||
|
||||
let iconView: UIImageView
|
||||
if let current = self.iconViews[id] {
|
||||
iconView = current
|
||||
} else {
|
||||
iconView = UIImageView()
|
||||
self.iconViews[id] = iconView
|
||||
self.view.addSubview(iconView)
|
||||
}
|
||||
iconView.image = icon
|
||||
iconView.tintColor = self.imageTintColor
|
||||
|
||||
let iconSize = icon.size
|
||||
if size.width != 0.0 {
|
||||
size.width += 3.0
|
||||
}
|
||||
iconView.frame = CGRect(origin: CGPoint(x: size.width, y: 3.0 + UIScreenPixel), size: iconSize)
|
||||
size.width += iconSize.width
|
||||
size.width += 3.0
|
||||
size.height = max(size.height, iconSize.height)
|
||||
}
|
||||
|
||||
if size.width >= constrainedSize.width {
|
||||
size.width = constrainedSize.width
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var removeTextIds: [Int] = []
|
||||
for (id, textNode) in self.textNodes {
|
||||
if !validTextIds.contains(id) {
|
||||
textNode.removeFromSupernode()
|
||||
removeTextIds.append(id)
|
||||
}
|
||||
}
|
||||
for id in removeTextIds {
|
||||
self.textNodes.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
var removeIconIds: [Int] = []
|
||||
for (id, iconView) in self.iconViews {
|
||||
if !validIconIds.contains(id) {
|
||||
iconView.removeFromSuperview()
|
||||
removeIconIds.append(id)
|
||||
}
|
||||
}
|
||||
for id in removeIconIds {
|
||||
self.iconViews.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
@ -1392,8 +1392,8 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
|
||||
toggleMessagesSelection: { messageId, selected in
|
||||
chatControllerInteraction.toggleMessagesSelection(messageId, selected)
|
||||
},
|
||||
openUrl: { url, param1, param2, message in
|
||||
chatControllerInteraction.openUrl(url, param1, param2, message, nil)
|
||||
openUrl: { url, concealed, external, message in
|
||||
chatControllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: concealed, external: external, message: message))
|
||||
},
|
||||
openInstantPage: { message, data in
|
||||
chatControllerInteraction.openInstantPage(message, data)
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "forwardtools_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,206 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 6.026367 4.379944 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
10.631250 13.370056 m
|
||||
10.631250 12.540056 l
|
||||
11.089646 12.540056 11.461250 12.911659 11.461250 13.370056 c
|
||||
10.631250 13.370056 l
|
||||
h
|
||||
10.631250 5.870056 m
|
||||
11.461250 5.870056 l
|
||||
11.461250 6.090186 11.373804 6.301300 11.218149 6.456955 c
|
||||
11.062494 6.612610 10.851380 6.700056 10.631250 6.700056 c
|
||||
10.631250 5.870056 l
|
||||
h
|
||||
1.188047 1.981661 m
|
||||
0.561160 2.525639 l
|
||||
1.188047 1.981661 l
|
||||
h
|
||||
12.315193 1.823753 m
|
||||
12.882865 1.218237 l
|
||||
12.315193 1.823753 l
|
||||
h
|
||||
19.853075 10.349594 m
|
||||
19.285404 9.744079 l
|
||||
19.853075 10.349594 l
|
||||
h
|
||||
19.853075 8.890519 m
|
||||
19.285404 9.496035 l
|
||||
19.853075 8.890519 l
|
||||
h
|
||||
12.315191 17.416361 m
|
||||
12.882863 18.021877 l
|
||||
12.315191 17.416361 l
|
||||
h
|
||||
11.461250 13.370056 m
|
||||
11.461250 16.686825 l
|
||||
9.801250 16.686825 l
|
||||
9.801250 13.370056 l
|
||||
11.461250 13.370056 l
|
||||
h
|
||||
11.747520 16.810844 m
|
||||
19.285404 9.744079 l
|
||||
20.420746 10.955110 l
|
||||
12.882863 18.021877 l
|
||||
11.747520 16.810844 l
|
||||
h
|
||||
19.285404 9.496035 m
|
||||
11.747522 2.429268 l
|
||||
12.882865 1.218237 l
|
||||
20.420748 8.285003 l
|
||||
19.285404 9.496035 l
|
||||
h
|
||||
11.461250 2.553288 m
|
||||
11.461250 5.870056 l
|
||||
9.801250 5.870056 l
|
||||
9.801250 2.553288 l
|
||||
11.461250 2.553288 l
|
||||
h
|
||||
10.631250 6.700056 m
|
||||
5.229464 6.700056 2.125496 4.328404 0.561160 2.525639 c
|
||||
1.814935 1.437683 l
|
||||
3.118428 2.939848 5.787818 5.040056 10.631250 5.040056 c
|
||||
10.631250 6.700056 l
|
||||
h
|
||||
0.915461 2.267101 m
|
||||
1.091532 4.065034 1.607588 6.660966 3.036170 8.786676 c
|
||||
4.431379 10.862727 6.724575 12.540056 10.631250 12.540056 c
|
||||
10.631250 14.200056 l
|
||||
6.139981 14.200056 3.343834 12.220509 1.658399 9.712606 c
|
||||
0.006337 7.254363 -0.549554 4.339265 -0.736636 2.428890 c
|
||||
0.915461 2.267101 l
|
||||
h
|
||||
0.561160 2.525639 m
|
||||
0.611023 2.583103 0.667563 2.604307 0.696526 2.610254 c
|
||||
0.721157 2.615311 0.734035 2.612202 0.741838 2.609715 c
|
||||
0.747947 2.607769 0.788049 2.594332 0.833411 2.538847 c
|
||||
0.888431 2.471550 0.925537 2.369989 0.915461 2.267101 c
|
||||
-0.736636 2.428890 l
|
||||
-0.813821 1.640717 -0.224644 1.175383 0.238040 1.028011 c
|
||||
0.696962 0.881838 1.366337 0.920712 1.814935 1.437683 c
|
||||
0.561160 2.525639 l
|
||||
h
|
||||
11.747522 2.429268 m
|
||||
11.712430 2.396370 11.683138 2.386383 11.659966 2.382921 c
|
||||
11.632549 2.378824 11.598459 2.382227 11.563670 2.397299 c
|
||||
11.528881 2.412371 11.503082 2.434914 11.487321 2.457716 c
|
||||
11.473999 2.476988 11.461250 2.505186 11.461250 2.553288 c
|
||||
9.801250 2.553288 l
|
||||
9.801250 0.951229 11.714108 0.122528 12.882865 1.218237 c
|
||||
11.747522 2.429268 l
|
||||
h
|
||||
19.285404 9.744079 m
|
||||
19.357044 9.676915 19.357044 9.563196 19.285404 9.496035 c
|
||||
20.420748 8.285003 l
|
||||
21.191931 9.007989 21.191929 10.232126 20.420746 10.955110 c
|
||||
19.285404 9.744079 l
|
||||
h
|
||||
11.461250 16.686825 m
|
||||
11.461250 16.734926 11.473999 16.763124 11.487321 16.782398 c
|
||||
11.503083 16.805201 11.528882 16.827742 11.563670 16.842813 c
|
||||
11.598459 16.857885 11.632549 16.861290 11.659966 16.857193 c
|
||||
11.683137 16.853729 11.712428 16.843744 11.747520 16.810844 c
|
||||
12.882863 18.021877 l
|
||||
11.714105 19.117588 9.801250 18.288879 9.801250 16.686825 c
|
||||
11.461250 16.686825 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 1.334961 19.835052 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
2.165001 7.999987 m
|
||||
1.703843 7.999987 1.330001 7.626144 1.330001 7.164987 c
|
||||
1.330001 6.703829 1.703843 6.329987 2.165001 6.329987 c
|
||||
2.626159 6.329987 3.000001 6.703829 3.000001 7.164987 c
|
||||
3.000001 7.626144 2.626159 7.999987 2.165001 7.999987 c
|
||||
h
|
||||
0.000000 7.164987 m
|
||||
0.000000 8.360683 0.969304 9.329987 2.165001 9.329987 c
|
||||
3.128699 9.329987 3.945334 8.700336 4.225954 7.829987 c
|
||||
9.665001 7.829987 l
|
||||
10.032270 7.829987 10.330001 7.532256 10.330001 7.164987 c
|
||||
10.330001 6.797717 10.032270 6.499987 9.665001 6.499987 c
|
||||
4.225954 6.499987 l
|
||||
3.945334 5.629638 3.128699 4.999987 2.165001 4.999987 c
|
||||
0.969304 4.999987 0.000000 5.969290 0.000000 7.164987 c
|
||||
h
|
||||
1.165001 2.829987 m
|
||||
0.797732 2.829987 0.500001 2.532256 0.500001 2.164987 c
|
||||
0.500001 1.797717 0.797732 1.499987 1.165001 1.499987 c
|
||||
6.104048 1.499987 l
|
||||
6.384667 0.629638 7.201303 -0.000013 8.165001 -0.000013 c
|
||||
9.360698 -0.000013 10.330001 0.969290 10.330001 2.164987 c
|
||||
10.330001 3.360683 9.360698 4.329987 8.165001 4.329987 c
|
||||
7.201303 4.329987 6.384668 3.700336 6.104048 2.829987 c
|
||||
1.165001 2.829987 l
|
||||
h
|
||||
7.330001 2.164987 m
|
||||
7.330001 2.626144 7.703843 2.999987 8.165001 2.999987 c
|
||||
8.626159 2.999987 9.000001 2.626144 9.000001 2.164987 c
|
||||
9.000001 1.703829 8.626159 1.329987 8.165001 1.329987 c
|
||||
7.703843 1.329987 7.330001 1.703829 7.330001 2.164987 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
4389
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000004479 00000 n
|
||||
0000004502 00000 n
|
||||
0000004675 00000 n
|
||||
0000004749 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
4808
|
||||
%%EOF
|
||||
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "linktools_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,139 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 1.334961 19.835052 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
2.165001 7.999987 m
|
||||
1.703843 7.999987 1.330001 7.626144 1.330001 7.164987 c
|
||||
1.330001 6.703829 1.703843 6.329987 2.165001 6.329987 c
|
||||
2.626159 6.329987 3.000001 6.703829 3.000001 7.164987 c
|
||||
3.000001 7.626144 2.626159 7.999987 2.165001 7.999987 c
|
||||
h
|
||||
0.000000 7.164987 m
|
||||
0.000000 8.360683 0.969304 9.329987 2.165001 9.329987 c
|
||||
3.128699 9.329987 3.945334 8.700336 4.225954 7.829987 c
|
||||
9.665001 7.829987 l
|
||||
10.032270 7.829987 10.330001 7.532256 10.330001 7.164987 c
|
||||
10.330001 6.797717 10.032270 6.499987 9.665001 6.499987 c
|
||||
4.225954 6.499987 l
|
||||
3.945334 5.629638 3.128699 4.999987 2.165001 4.999987 c
|
||||
0.969304 4.999987 0.000000 5.969290 0.000000 7.164987 c
|
||||
h
|
||||
1.165001 2.829987 m
|
||||
0.797732 2.829987 0.500001 2.532256 0.500001 2.164987 c
|
||||
0.500001 1.797717 0.797732 1.499987 1.165001 1.499987 c
|
||||
6.104048 1.499987 l
|
||||
6.384667 0.629638 7.201303 -0.000013 8.165001 -0.000013 c
|
||||
9.360698 -0.000013 10.330001 0.969290 10.330001 2.164987 c
|
||||
10.330001 3.360683 9.360698 4.329987 8.165001 4.329987 c
|
||||
7.201303 4.329987 6.384668 3.700336 6.104048 2.829987 c
|
||||
1.165001 2.829987 l
|
||||
h
|
||||
7.330001 2.164987 m
|
||||
7.330001 2.626144 7.703843 2.999987 8.165001 2.999987 c
|
||||
8.626159 2.999987 9.000001 2.626144 9.000001 2.164987 c
|
||||
9.000001 1.703829 8.626159 1.329987 8.165001 1.329987 c
|
||||
7.703843 1.329987 7.330001 1.703829 7.330001 2.164987 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 6.120117 1.915771 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
12.468664 19.994034 m
|
||||
14.215597 21.740967 17.047935 21.740967 18.794867 19.994034 c
|
||||
20.541800 18.247101 20.541800 15.414764 18.794867 13.667831 c
|
||||
16.294867 11.167831 l
|
||||
14.547935 9.420898 11.715597 9.420898 9.968664 11.167831 c
|
||||
9.736349 11.400146 9.535478 11.650909 9.365597 11.915075 c
|
||||
9.117653 12.300628 8.604102 12.412183 8.218549 12.164239 c
|
||||
7.832996 11.916295 7.721442 11.402744 7.969386 11.017192 c
|
||||
8.202941 10.654012 8.478252 10.310648 8.794867 9.994034 c
|
||||
11.190069 7.598831 15.073462 7.598831 17.468664 9.994034 c
|
||||
19.968664 12.494034 l
|
||||
22.363867 14.889237 22.363867 18.772629 19.968664 21.167831 c
|
||||
17.573462 23.563034 13.690069 23.563034 11.294867 21.167831 c
|
||||
8.794867 18.667831 l
|
||||
8.470732 18.343697 8.470732 17.818169 8.794867 17.494034 c
|
||||
9.119002 17.169899 9.644529 17.169899 9.968664 17.494034 c
|
||||
12.468664 19.994034 l
|
||||
h
|
||||
9.296402 4.167830 m
|
||||
7.549469 2.420897 4.717132 2.420897 2.970200 4.167830 c
|
||||
1.223267 5.914762 1.223267 8.747099 2.970200 10.494032 c
|
||||
5.470199 12.994032 l
|
||||
7.217132 14.740965 10.049469 14.740965 11.796402 12.994032 c
|
||||
12.028717 12.761717 12.229589 12.510954 12.399469 12.246788 c
|
||||
12.647413 11.861236 13.160964 11.749681 13.546517 11.997624 c
|
||||
13.932070 12.245568 14.043624 12.759119 13.795681 13.144671 c
|
||||
13.562125 13.507852 13.286814 13.851215 12.970200 14.167830 c
|
||||
10.574997 16.563032 6.691605 16.563032 4.296402 14.167830 c
|
||||
1.796402 11.667830 l
|
||||
-0.598801 9.272626 -0.598801 5.389235 1.796402 2.994032 c
|
||||
4.191605 0.598829 8.074997 0.598829 10.470200 2.994032 c
|
||||
12.970200 5.494032 l
|
||||
13.294334 5.818167 13.294334 6.343695 12.970200 6.667830 c
|
||||
12.646064 6.991964 12.120537 6.991964 11.796402 6.667830 c
|
||||
9.296402 4.167830 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
3174
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000003264 00000 n
|
||||
0000003287 00000 n
|
||||
0000003460 00000 n
|
||||
0000003534 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
3593
|
||||
%%EOF
|
||||
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "channel.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 1.000000 0.572754 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
2.171946 3.261456 m
|
||||
3.602304 3.261456 l
|
||||
3.736763 3.261456 3.803992 3.261456 3.869396 3.256588 c
|
||||
4.171662 3.234089 4.463039 3.134116 4.715446 2.966301 c
|
||||
4.770054 2.929994 4.823115 2.888726 4.929220 2.806200 c
|
||||
4.929265 2.806165 l
|
||||
6.192761 1.823446 l
|
||||
7.167630 1.065215 7.655064 0.686099 8.064308 0.689581 c
|
||||
8.420319 0.692611 8.755808 0.856692 8.976768 1.135851 c
|
||||
9.230768 1.456751 9.230768 2.074262 9.230768 3.309284 c
|
||||
9.230768 7.557522 l
|
||||
9.230768 8.792542 9.230768 9.410053 8.976768 9.730952 c
|
||||
8.755808 10.010111 8.420319 10.174193 8.064308 10.177222 c
|
||||
7.655064 10.180704 7.167630 9.801589 6.192762 9.043358 c
|
||||
4.929265 8.060638 l
|
||||
4.929246 8.060623 l
|
||||
4.929197 8.060584 l
|
||||
4.823106 7.978071 4.770050 7.936806 4.715446 7.900502 c
|
||||
4.463039 7.732687 4.171662 7.632714 3.869396 7.610216 c
|
||||
3.803992 7.605347 3.736763 7.605347 3.602304 7.605347 c
|
||||
2.171948 7.605347 l
|
||||
0.972415 7.605347 0.000000 6.632934 0.000000 5.433402 c
|
||||
0.000000 4.233869 0.972414 3.261456 2.171946 3.261456 c
|
||||
h
|
||||
2.206053 2.149403 m
|
||||
2.171725 2.053373 2.171725 1.933668 2.171725 1.694258 c
|
||||
2.171725 1.089490 l
|
||||
2.171725 0.489724 2.657931 0.003517 3.257698 0.003517 c
|
||||
3.857464 0.003517 4.343671 0.489724 4.343671 1.089491 c
|
||||
4.343671 1.694259 l
|
||||
4.343671 1.933668 4.343671 2.053373 4.309342 2.149403 c
|
||||
4.250016 2.315361 4.119403 2.445974 3.953444 2.505301 c
|
||||
3.857416 2.539629 3.737711 2.539629 3.498301 2.539629 c
|
||||
3.017095 2.539629 l
|
||||
2.777685 2.539629 2.657980 2.539629 2.561951 2.505301 c
|
||||
2.395993 2.445974 2.265379 2.315361 2.206053 2.149403 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1626
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 12.000000 11.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001716 00000 n
|
||||
0000001739 00000 n
|
||||
0000001912 00000 n
|
||||
0000001986 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
2045
|
||||
%%EOF
|
||||
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "group.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,162 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< /Type /XObject
|
||||
/Length 2 0 R
|
||||
/Group << /Type /Group
|
||||
/S /Transparency
|
||||
>>
|
||||
/Subtype /Form
|
||||
/Resources << >>
|
||||
/BBox [ 0.000000 0.000000 12.000000 11.000000 ]
|
||||
>>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 0.000000 0.549805 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
4.210876 5.670196 m
|
||||
5.254694 5.670196 6.100876 6.516377 6.100876 7.560195 c
|
||||
6.100876 8.604013 5.254694 9.450195 4.210876 9.450195 c
|
||||
3.167058 9.450195 2.320876 8.604013 2.320876 7.560195 c
|
||||
2.320876 6.516377 3.167058 5.670196 4.210876 5.670196 c
|
||||
h
|
||||
8.530897 5.670156 m
|
||||
9.425598 5.670156 10.150897 6.395455 10.150897 7.290156 c
|
||||
10.150897 8.184857 9.425598 8.910156 8.530897 8.910156 c
|
||||
7.636196 8.910156 6.910897 8.184857 6.910897 7.290156 c
|
||||
6.910897 6.395455 7.636196 5.670156 8.530897 5.670156 c
|
||||
h
|
||||
4.211036 4.319980 m
|
||||
6.565200 4.319980 7.711937 3.183578 8.270525 2.076473 c
|
||||
8.807893 1.011423 7.833971 -0.000021 6.641036 -0.000021 c
|
||||
1.781036 -0.000021 l
|
||||
0.588101 -0.000021 -0.385821 1.011424 0.151547 2.076473 c
|
||||
0.710135 3.183578 1.856872 4.319980 4.211036 4.319980 c
|
||||
h
|
||||
7.304841 4.215691 m
|
||||
8.083344 3.710282 8.587383 3.042050 8.911377 2.399903 c
|
||||
9.138542 1.949668 9.186308 1.495334 9.099791 1.080082 c
|
||||
10.421161 1.080082 l
|
||||
11.315862 1.080082 12.045328 1.837108 11.649654 2.639562 c
|
||||
11.227840 3.495031 10.351560 4.383612 8.531160 4.383612 c
|
||||
8.059293 4.383612 7.650861 4.323907 7.297337 4.220552 c
|
||||
7.304841 4.215691 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
1240
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
<< /Type /XObject
|
||||
/Length 4 0 R
|
||||
/Group << /Type /Group
|
||||
/S /Transparency
|
||||
>>
|
||||
/Subtype /Form
|
||||
/Resources << >>
|
||||
/BBox [ 0.000000 0.000000 12.000000 11.000000 ]
|
||||
>>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.000000 11.000000 m
|
||||
12.000000 11.000000 l
|
||||
12.000000 0.000000 l
|
||||
0.000000 0.000000 l
|
||||
0.000000 11.000000 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
232
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /XObject << /X1 1 0 R >>
|
||||
/ExtGState << /E1 << /SMask << /Type /Mask
|
||||
/G 3 0 R
|
||||
/S /Alpha
|
||||
>>
|
||||
/Type /ExtGState
|
||||
>> >>
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Length 7 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
/E1 gs
|
||||
/X1 Do
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
7 0 obj
|
||||
46
|
||||
endobj
|
||||
|
||||
8 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 12.000000 11.000000 ]
|
||||
/Resources 5 0 R
|
||||
/Contents 6 0 R
|
||||
/Parent 9 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
9 0 obj
|
||||
<< /Kids [ 8 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
10 0 obj
|
||||
<< /Pages 9 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 11
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000001498 00000 n
|
||||
0000001521 00000 n
|
||||
0000002001 00000 n
|
||||
0000002023 00000 n
|
||||
0000002321 00000 n
|
||||
0000002423 00000 n
|
||||
0000002444 00000 n
|
||||
0000002617 00000 n
|
||||
0000002691 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 10 0 R
|
||||
/Size 11
|
||||
>>
|
||||
startxref
|
||||
2751
|
||||
%%EOF
|
||||
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "replyquote_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,198 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
-1.000000 -0.000000 -0.000000 1.000000 23.973633 4.379944 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
10.631250 13.370056 m
|
||||
10.631250 12.540056 l
|
||||
11.089646 12.540056 11.461250 12.911659 11.461250 13.370056 c
|
||||
10.631250 13.370056 l
|
||||
h
|
||||
10.631250 5.870056 m
|
||||
11.461250 5.870056 l
|
||||
11.461250 6.090186 11.373804 6.301300 11.218149 6.456955 c
|
||||
11.062494 6.612610 10.851380 6.700056 10.631250 6.700056 c
|
||||
10.631250 5.870056 l
|
||||
h
|
||||
1.188047 1.981661 m
|
||||
0.561160 2.525639 l
|
||||
1.188047 1.981661 l
|
||||
h
|
||||
12.315193 1.823753 m
|
||||
12.882865 1.218237 l
|
||||
12.315193 1.823753 l
|
||||
h
|
||||
19.853075 10.349594 m
|
||||
19.285404 9.744079 l
|
||||
19.853075 10.349594 l
|
||||
h
|
||||
19.853075 8.890519 m
|
||||
19.285404 9.496035 l
|
||||
19.853075 8.890519 l
|
||||
h
|
||||
12.315191 17.416361 m
|
||||
12.882863 18.021877 l
|
||||
12.315191 17.416361 l
|
||||
h
|
||||
11.461250 13.370056 m
|
||||
11.461250 16.686825 l
|
||||
9.801250 16.686825 l
|
||||
9.801250 13.370056 l
|
||||
11.461250 13.370056 l
|
||||
h
|
||||
11.747520 16.810844 m
|
||||
19.285404 9.744079 l
|
||||
20.420746 10.955110 l
|
||||
12.882863 18.021877 l
|
||||
11.747520 16.810844 l
|
||||
h
|
||||
19.285404 9.496035 m
|
||||
11.747522 2.429268 l
|
||||
12.882865 1.218237 l
|
||||
20.420748 8.285003 l
|
||||
19.285404 9.496035 l
|
||||
h
|
||||
11.461250 2.553288 m
|
||||
11.461250 5.870056 l
|
||||
9.801250 5.870056 l
|
||||
9.801250 2.553288 l
|
||||
11.461250 2.553288 l
|
||||
h
|
||||
10.631250 6.700056 m
|
||||
5.229464 6.700056 2.125496 4.328404 0.561160 2.525639 c
|
||||
1.814935 1.437683 l
|
||||
3.118428 2.939848 5.787818 5.040056 10.631250 5.040056 c
|
||||
10.631250 6.700056 l
|
||||
h
|
||||
0.915461 2.267101 m
|
||||
1.091532 4.065034 1.607588 6.660966 3.036170 8.786676 c
|
||||
4.431379 10.862727 6.724575 12.540056 10.631250 12.540056 c
|
||||
10.631250 14.200056 l
|
||||
6.139981 14.200056 3.343834 12.220509 1.658399 9.712606 c
|
||||
0.006337 7.254363 -0.549554 4.339265 -0.736636 2.428890 c
|
||||
0.915461 2.267101 l
|
||||
h
|
||||
0.561160 2.525639 m
|
||||
0.611023 2.583103 0.667563 2.604307 0.696526 2.610254 c
|
||||
0.721157 2.615311 0.734035 2.612202 0.741838 2.609715 c
|
||||
0.747947 2.607769 0.788049 2.594332 0.833411 2.538847 c
|
||||
0.888431 2.471550 0.925537 2.369989 0.915461 2.267101 c
|
||||
-0.736636 2.428890 l
|
||||
-0.813821 1.640717 -0.224644 1.175383 0.238040 1.028011 c
|
||||
0.696962 0.881838 1.366337 0.920712 1.814935 1.437683 c
|
||||
0.561160 2.525639 l
|
||||
h
|
||||
11.747522 2.429268 m
|
||||
11.712430 2.396370 11.683138 2.386383 11.659966 2.382921 c
|
||||
11.632549 2.378824 11.598459 2.382227 11.563670 2.397299 c
|
||||
11.528881 2.412371 11.503082 2.434914 11.487321 2.457716 c
|
||||
11.473999 2.476988 11.461250 2.505186 11.461250 2.553288 c
|
||||
9.801250 2.553288 l
|
||||
9.801250 0.951229 11.714108 0.122528 12.882865 1.218237 c
|
||||
11.747522 2.429268 l
|
||||
h
|
||||
19.285404 9.744079 m
|
||||
19.357044 9.676915 19.357044 9.563196 19.285404 9.496035 c
|
||||
20.420748 8.285003 l
|
||||
21.191931 9.007989 21.191929 10.232126 20.420746 10.955110 c
|
||||
19.285404 9.744079 l
|
||||
h
|
||||
11.461250 16.686825 m
|
||||
11.461250 16.734926 11.473999 16.763124 11.487321 16.782398 c
|
||||
11.503083 16.805201 11.528882 16.827742 11.563670 16.842813 c
|
||||
11.598459 16.857885 11.632549 16.861290 11.659966 16.857193 c
|
||||
11.683137 16.853729 11.712428 16.843744 11.747520 16.810844 c
|
||||
12.882863 18.021877 l
|
||||
11.714105 19.117588 9.801250 18.288879 9.801250 16.686825 c
|
||||
11.461250 16.686825 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 19.000000 20.858582 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.000000 5.141418 m
|
||||
0.000000 6.245988 0.895431 7.141418 2.000000 7.141418 c
|
||||
3.104569 7.141418 4.000000 6.245988 4.000000 5.141418 c
|
||||
4.000000 4.557826 l
|
||||
4.000000 3.471111 3.746984 2.399319 3.260991 1.427331 c
|
||||
2.894427 0.694205 l
|
||||
2.647438 0.200226 2.046765 0.000002 1.552786 0.246991 c
|
||||
1.058808 0.493980 0.858584 1.094654 1.105573 1.588632 c
|
||||
1.472136 2.321758 l
|
||||
1.605720 2.588927 1.714662 2.866652 1.798144 3.151479 c
|
||||
0.788369 3.252682 0.000000 4.104984 0.000000 5.141418 c
|
||||
h
|
||||
5.000000 5.141418 m
|
||||
5.000000 6.245988 5.895431 7.141418 7.000000 7.141418 c
|
||||
8.104569 7.141418 9.000000 6.245988 9.000000 5.141418 c
|
||||
9.000000 4.557826 l
|
||||
9.000000 3.471111 8.746984 2.399319 8.260990 1.427331 c
|
||||
7.894427 0.694205 l
|
||||
7.647438 0.200226 7.046765 0.000002 6.552786 0.246991 c
|
||||
6.058808 0.493980 5.858583 1.094654 6.105573 1.588632 c
|
||||
6.472136 2.321758 l
|
||||
6.605721 2.588927 6.714662 2.866652 6.798144 3.151479 c
|
||||
5.788369 3.252682 5.000000 4.104984 5.000000 5.141418 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
4042
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000004132 00000 n
|
||||
0000004155 00000 n
|
||||
0000004328 00000 n
|
||||
0000004402 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
4461
|
||||
%%EOF
|
||||
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "replytools_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,206 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
-1.000000 -0.000000 -0.000000 1.000000 23.973633 4.379944 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
10.631250 13.370056 m
|
||||
10.631250 12.540056 l
|
||||
11.089646 12.540056 11.461250 12.911659 11.461250 13.370056 c
|
||||
10.631250 13.370056 l
|
||||
h
|
||||
10.631250 5.870056 m
|
||||
11.461250 5.870056 l
|
||||
11.461250 6.090186 11.373804 6.301300 11.218149 6.456955 c
|
||||
11.062494 6.612610 10.851380 6.700056 10.631250 6.700056 c
|
||||
10.631250 5.870056 l
|
||||
h
|
||||
1.188047 1.981661 m
|
||||
0.561160 2.525639 l
|
||||
1.188047 1.981661 l
|
||||
h
|
||||
12.315193 1.823753 m
|
||||
12.882865 1.218237 l
|
||||
12.315193 1.823753 l
|
||||
h
|
||||
19.853075 10.349594 m
|
||||
19.285404 9.744079 l
|
||||
19.853075 10.349594 l
|
||||
h
|
||||
19.853075 8.890519 m
|
||||
19.285404 9.496035 l
|
||||
19.853075 8.890519 l
|
||||
h
|
||||
12.315191 17.416361 m
|
||||
12.882863 18.021877 l
|
||||
12.315191 17.416361 l
|
||||
h
|
||||
11.461250 13.370056 m
|
||||
11.461250 16.686825 l
|
||||
9.801250 16.686825 l
|
||||
9.801250 13.370056 l
|
||||
11.461250 13.370056 l
|
||||
h
|
||||
11.747520 16.810844 m
|
||||
19.285404 9.744079 l
|
||||
20.420746 10.955110 l
|
||||
12.882863 18.021877 l
|
||||
11.747520 16.810844 l
|
||||
h
|
||||
19.285404 9.496035 m
|
||||
11.747522 2.429268 l
|
||||
12.882865 1.218237 l
|
||||
20.420748 8.285003 l
|
||||
19.285404 9.496035 l
|
||||
h
|
||||
11.461250 2.553288 m
|
||||
11.461250 5.870056 l
|
||||
9.801250 5.870056 l
|
||||
9.801250 2.553288 l
|
||||
11.461250 2.553288 l
|
||||
h
|
||||
10.631250 6.700056 m
|
||||
5.229464 6.700056 2.125496 4.328404 0.561160 2.525639 c
|
||||
1.814935 1.437683 l
|
||||
3.118428 2.939848 5.787818 5.040056 10.631250 5.040056 c
|
||||
10.631250 6.700056 l
|
||||
h
|
||||
0.915461 2.267101 m
|
||||
1.091532 4.065034 1.607588 6.660966 3.036170 8.786676 c
|
||||
4.431379 10.862727 6.724575 12.540056 10.631250 12.540056 c
|
||||
10.631250 14.200056 l
|
||||
6.139981 14.200056 3.343834 12.220509 1.658399 9.712606 c
|
||||
0.006337 7.254363 -0.549554 4.339265 -0.736636 2.428890 c
|
||||
0.915461 2.267101 l
|
||||
h
|
||||
0.561160 2.525639 m
|
||||
0.611023 2.583103 0.667563 2.604307 0.696526 2.610254 c
|
||||
0.721157 2.615311 0.734035 2.612202 0.741838 2.609715 c
|
||||
0.747947 2.607769 0.788049 2.594332 0.833411 2.538847 c
|
||||
0.888431 2.471550 0.925537 2.369989 0.915461 2.267101 c
|
||||
-0.736636 2.428890 l
|
||||
-0.813821 1.640717 -0.224644 1.175383 0.238040 1.028011 c
|
||||
0.696962 0.881838 1.366337 0.920712 1.814935 1.437683 c
|
||||
0.561160 2.525639 l
|
||||
h
|
||||
11.747522 2.429268 m
|
||||
11.712430 2.396370 11.683138 2.386383 11.659966 2.382921 c
|
||||
11.632549 2.378824 11.598459 2.382227 11.563670 2.397299 c
|
||||
11.528881 2.412371 11.503082 2.434914 11.487321 2.457716 c
|
||||
11.473999 2.476988 11.461250 2.505186 11.461250 2.553288 c
|
||||
9.801250 2.553288 l
|
||||
9.801250 0.951229 11.714108 0.122528 12.882865 1.218237 c
|
||||
11.747522 2.429268 l
|
||||
h
|
||||
19.285404 9.744079 m
|
||||
19.357044 9.676915 19.357044 9.563196 19.285404 9.496035 c
|
||||
20.420748 8.285003 l
|
||||
21.191931 9.007989 21.191929 10.232126 20.420746 10.955110 c
|
||||
19.285404 9.744079 l
|
||||
h
|
||||
11.461250 16.686825 m
|
||||
11.461250 16.734926 11.473999 16.763124 11.487321 16.782398 c
|
||||
11.503083 16.805201 11.528882 16.827742 11.563670 16.842813 c
|
||||
11.598459 16.857885 11.632549 16.861290 11.659966 16.857193 c
|
||||
11.683137 16.853729 11.712428 16.843744 11.747520 16.810844 c
|
||||
12.882863 18.021877 l
|
||||
11.714105 19.117588 9.801250 18.288879 9.801250 16.686825 c
|
||||
11.461250 16.686825 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 18.334961 19.840027 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
2.165001 7.999987 m
|
||||
1.703843 7.999987 1.330001 7.626144 1.330001 7.164987 c
|
||||
1.330001 6.703829 1.703843 6.329987 2.165001 6.329987 c
|
||||
2.626159 6.329987 3.000001 6.703829 3.000001 7.164987 c
|
||||
3.000001 7.626144 2.626159 7.999987 2.165001 7.999987 c
|
||||
h
|
||||
2.165001 9.329987 m
|
||||
3.128699 9.329987 3.945334 8.700336 4.225954 7.829987 c
|
||||
9.165001 7.829987 l
|
||||
9.532270 7.829987 9.830001 7.532256 9.830001 7.164987 c
|
||||
9.830001 6.797717 9.532270 6.499987 9.165001 6.499987 c
|
||||
4.225954 6.499987 l
|
||||
3.945334 5.629638 3.128699 4.999987 2.165001 4.999987 c
|
||||
0.969304 4.999987 0.000000 5.969290 0.000000 7.164987 c
|
||||
0.000000 8.360683 0.969304 9.329987 2.165001 9.329987 c
|
||||
h
|
||||
1.165001 2.829987 m
|
||||
0.797732 2.829987 0.500001 2.532256 0.500001 2.164987 c
|
||||
0.500001 1.797717 0.797732 1.499987 1.165001 1.499987 c
|
||||
6.104048 1.499987 l
|
||||
6.384667 0.629638 7.201303 -0.000013 8.165001 -0.000013 c
|
||||
9.360698 -0.000013 10.330001 0.969290 10.330001 2.164987 c
|
||||
10.330001 3.360683 9.360698 4.329987 8.165001 4.329987 c
|
||||
7.201303 4.329987 6.384668 3.700336 6.104048 2.829987 c
|
||||
1.165001 2.829987 l
|
||||
h
|
||||
7.330001 2.164987 m
|
||||
7.330001 1.703829 7.703843 1.329987 8.165001 1.329987 c
|
||||
8.626159 1.329987 9.000001 1.703829 9.000001 2.164987 c
|
||||
9.000001 2.626144 8.626159 2.999987 8.165001 2.999987 c
|
||||
7.703843 2.999987 7.330001 2.626144 7.330001 2.164987 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
4388
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000004478 00000 n
|
||||
0000004501 00000 n
|
||||
0000004674 00000 n
|
||||
0000004748 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
4807
|
||||
%%EOF
|
||||
@ -2743,8 +2743,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.openPeer(peer: nil, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: nil, peekData: nil), fromMessage: nil, peerTypes: peerTypes)
|
||||
}
|
||||
}
|
||||
}, openUrl: { [weak self] url, concealed, _, message, progress in
|
||||
}, openUrl: { [weak self] urlData in
|
||||
if let strongSelf = self {
|
||||
let url = urlData.url
|
||||
let concealed = urlData.concealed
|
||||
let message = urlData.message
|
||||
let progress = urlData.progress
|
||||
|
||||
var skipConcealedAlert = false
|
||||
if let author = message?.author, author.isVerified {
|
||||
skipConcealedAlert = true
|
||||
@ -2758,7 +2763,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
performOpenURL(message, url, progress)
|
||||
} else {
|
||||
progress?.set(.single(false))
|
||||
strongSelf.openUrl(url, concealed: concealed, skipConcealedAlert: skipConcealedAlert, message: message)
|
||||
strongSelf.openUrl(url, concealed: concealed, skipConcealedAlert: skipConcealedAlert, message: message, allowInlineWebpageResolution: urlData.allowInlineWebpageResolution)
|
||||
}
|
||||
}
|
||||
}, shareCurrentLocation: { [weak self] in
|
||||
@ -3365,7 +3370,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
items.append(ActionSheetButtonItem(title: url.title, color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllerInteraction?.openUrl(url.url, false, false, message, nil)
|
||||
strongSelf.controllerInteraction?.openUrl(ChatControllerInteraction.OpenUrl(url: url.url, concealed: false, external: false, message: message))
|
||||
}
|
||||
}))
|
||||
}
|
||||
@ -3444,7 +3449,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
}, openSearch: {
|
||||
}, setupReply: { [weak self] messageId in
|
||||
self?.interfaceInteraction?.setupReplyMessage(messageId, { _ in })
|
||||
self?.interfaceInteraction?.setupReplyMessage(messageId, { _, _ in })
|
||||
}, canSetupReply: { [weak self] message in
|
||||
if !message.flags.contains(.Incoming) {
|
||||
if !message.flags.intersection([.Failed, .Sending, .Unsent]).isEmpty {
|
||||
@ -4609,7 +4614,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
case let .join(_, joinHash):
|
||||
self.controllerInteraction?.openJoinLink(joinHash)
|
||||
case let .webPage(_, url):
|
||||
self.controllerInteraction?.openUrl(url, false, false, nil, nil)
|
||||
self.controllerInteraction?.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: false, external: false))
|
||||
}
|
||||
}, openRequestedPeerSelection: { [weak self] messageId, peerType, buttonId in
|
||||
guard let self else {
|
||||
@ -8645,20 +8650,33 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
messageId: message.id,
|
||||
quote: nil
|
||||
))
|
||||
}).updatedSearch(nil).updatedShowCommands(false) }, completion: completion)
|
||||
}).updatedSearch(nil).updatedShowCommands(false) }, completion: { t in
|
||||
completion(t, {})
|
||||
})
|
||||
strongSelf.updateItemNodesSearchTextHighlightStates()
|
||||
strongSelf.chatDisplayNode.ensureInputViewFocused()
|
||||
} else {
|
||||
completion(.immediate)
|
||||
completion(.immediate, {})
|
||||
}
|
||||
}, alertAction: {
|
||||
completion(.immediate)
|
||||
completion(.immediate, {})
|
||||
}, delay: true)
|
||||
} else {
|
||||
completion(.immediate)
|
||||
let replySubject = ChatInterfaceState.ReplyMessageSubject(
|
||||
messageId: messageId,
|
||||
quote: nil
|
||||
)
|
||||
completion(.immediate, {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
moveReplyMessageToAnotherChat(selfController: self, replySubject: replySubject)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(nil) }) }, completion: completion)
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(nil) }) }, completion: { t in
|
||||
completion(t, {})
|
||||
})
|
||||
}
|
||||
}, setupEditMessage: { [weak self] messageId, completion in
|
||||
if let strongSelf = self, strongSelf.isNodeLoaded {
|
||||
@ -17970,10 +17988,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}, contentContext: nil)
|
||||
}
|
||||
|
||||
func openUrl(_ url: String, concealed: Bool, forceExternal: Bool = false, skipUrlAuth: Bool = false, skipConcealedAlert: Bool = false, message: Message? = nil, commit: @escaping () -> Void = {}) {
|
||||
func openUrl(_ url: String, concealed: Bool, forceExternal: Bool = false, skipUrlAuth: Bool = false, skipConcealedAlert: Bool = false, message: Message? = nil, allowInlineWebpageResolution: Bool = false, commit: @escaping () -> Void = {}) {
|
||||
self.commitPurposefulAction()
|
||||
|
||||
if let message, let webpage = message.media.first(where: { $0 is TelegramMediaWebpage }) as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, content.url == url {
|
||||
if allowInlineWebpageResolution, let message, let webpage = message.media.first(where: { $0 is TelegramMediaWebpage }) as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, content.url == url {
|
||||
if content.instantPage != nil {
|
||||
if let navigationController = self.navigationController as? NavigationController {
|
||||
switch instantPageType(of: content) {
|
||||
|
||||
@ -206,7 +206,7 @@ extension ListMessageItemInteraction {
|
||||
}, toggleMessagesSelection: { messageId, selected in
|
||||
controllerInteraction.toggleMessagesSelection(messageId, selected)
|
||||
}, openUrl: { url, param1, param2, message in
|
||||
controllerInteraction.openUrl(url, param1, param2, message, nil)
|
||||
controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: param1, external: param2, message: message))
|
||||
}, openInstantPage: { message, data in
|
||||
controllerInteraction.openInstantPage(message, data)
|
||||
}, longTap: { action, message in
|
||||
|
||||
@ -294,6 +294,9 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS
|
||||
if case .member = channel.participationStatus {
|
||||
canReply = channel.hasPermission(.sendSomething)
|
||||
}
|
||||
if case .broadcast = channel.info {
|
||||
canReply = true
|
||||
}
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
if case .Member = group.membership {
|
||||
canReply = true
|
||||
@ -958,9 +961,11 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
if !isPinnedMessages, !isReplyThreadHead, data.canReply {
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuReply, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reply"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
interfaceInteraction.setupReplyMessage(messages[0].id, { transition in
|
||||
f(.custom(transition))
|
||||
}, action: { c, _ in
|
||||
interfaceInteraction.setupReplyMessage(messages[0].id, { transition, completed in
|
||||
c.dismiss(result: .custom(transition), completion: {
|
||||
completed()
|
||||
})
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ import TextNodeWithEntities
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import TranslateUI
|
||||
import ChatControllerInteraction
|
||||
|
||||
private enum PinnedMessageAnimation {
|
||||
case slideToTop
|
||||
@ -858,7 +859,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
if url.hasPrefix("tg://") {
|
||||
isConcealed = false
|
||||
}
|
||||
controllerInteraction.openUrl(url, isConcealed, nil, nil, nil)
|
||||
controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: isConcealed))
|
||||
case .requestMap:
|
||||
controllerInteraction.shareCurrentLocation()
|
||||
case .requestPhone:
|
||||
|
||||
@ -98,7 +98,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
}, requestMessageActionCallback: { _, _, _, _ in
|
||||
}, requestMessageActionUrlAuth: { _, _ in
|
||||
}, activateSwitchInline: { _, _, _ in
|
||||
}, openUrl: { _, _, _, _, _ in
|
||||
}, openUrl: { _ in
|
||||
}, shareCurrentLocation: {
|
||||
}, shareAccountContact: {
|
||||
}, sendBotCommand: { _, _ in
|
||||
|
||||
@ -2779,11 +2779,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}, requestMessageActionCallback: { _, _, _, _ in
|
||||
}, requestMessageActionUrlAuth: { _, _ in
|
||||
}, activateSwitchInline: { _, _, _ in
|
||||
}, openUrl: { [weak self] url, concealed, external, _, _ in
|
||||
}, openUrl: { [weak self] url in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.openUrl(url: url, concealed: concealed, external: external ?? false)
|
||||
strongSelf.openUrl(url: url.url, concealed: url.concealed, external: url.external ?? false)
|
||||
}, shareCurrentLocation: {
|
||||
}, shareAccountContact: {
|
||||
}, sendBotCommand: { _, _ in
|
||||
|
||||
@ -1490,7 +1490,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
clickThroughMessage?()
|
||||
}, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in
|
||||
return false
|
||||
}, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { _, _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
}, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
}, presentController: { _, _ in
|
||||
}, presentControllerInCurrent: { _, _ in
|
||||
}, navigationController: {
|
||||
|
||||
@ -9,6 +9,7 @@ import AccountContext
|
||||
import TelegramStringFormatting
|
||||
import ChatPresentationInterfaceState
|
||||
import AccessoryPanelNode
|
||||
import AppBundle
|
||||
|
||||
final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
|
||||
private let webpageDisposable = MetaDisposable()
|
||||
@ -18,7 +19,7 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
|
||||
|
||||
let closeButton: HighlightableButtonNode
|
||||
let lineNode: ASImageNode
|
||||
let iconNode: ASImageNode
|
||||
let iconView: UIImageView
|
||||
let titleNode: TextNode
|
||||
private var titleString: NSAttributedString?
|
||||
|
||||
@ -46,10 +47,9 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
|
||||
self.lineNode.displaysAsynchronously = false
|
||||
self.lineNode.image = PresentationResourcesChat.chatInputPanelVerticalSeparatorLineImage(theme)
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.displayWithoutProcessing = false
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
self.iconNode.image = PresentationResourcesChat.chatInputPanelWebpageIconImage(theme)
|
||||
self.iconView = UIImageView()
|
||||
self.iconView.image = UIImage(bundleImageName: "Chat/Input/Accessory Panels/LinkSettingsIcon")?.withRenderingMode(.alwaysTemplate)
|
||||
self.iconView.tintColor = theme.chat.inputPanel.panelControlAccentColor
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.displaysAsynchronously = false
|
||||
@ -63,7 +63,7 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
|
||||
self.addSubnode(self.closeButton)
|
||||
|
||||
self.addSubnode(self.lineNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
self.view.addSubview(self.iconView)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
|
||||
@ -75,11 +75,11 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
|
||||
}
|
||||
|
||||
override func animateIn() {
|
||||
self.iconNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2)
|
||||
self.iconView.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
override func animateOut() {
|
||||
self.iconNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
|
||||
self.iconView.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
override public func didLoad() {
|
||||
@ -101,7 +101,7 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
|
||||
|
||||
self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(theme), for: [])
|
||||
self.lineNode.image = PresentationResourcesChat.chatInputPanelVerticalSeparatorLineImage(theme)
|
||||
self.iconNode.image = PresentationResourcesChat.chatInputPanelWebpageIconImage(theme)
|
||||
self.iconView.tintColor = theme.chat.inputPanel.panelControlAccentColor
|
||||
}
|
||||
|
||||
if let text = self.titleString?.string {
|
||||
@ -194,8 +194,8 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
|
||||
|
||||
self.lineNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 8.0), size: CGSize(width: 2.0, height: bounds.size.height - 10.0))
|
||||
|
||||
if let icon = self.iconNode.image {
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: 7.0 + inset, y: 10.0), size: icon.size)
|
||||
if let icon = self.iconView.image {
|
||||
self.iconView.frame = CGRect(origin: CGPoint(x: 7.0 + inset, y: 10.0), size: icon.size)
|
||||
}
|
||||
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user