Various improvements

This commit is contained in:
Ilya Laktyushin 2024-06-18 01:06:31 +04:00
parent 907e408dcc
commit a2348148ff
11 changed files with 250 additions and 39 deletions

View File

@ -12223,11 +12223,15 @@ Sorry for the inconvenience.";
"Chat.Context.Username.Copy" = "Copy Username"; "Chat.Context.Username.Copy" = "Copy Username";
"Chat.Context.Username.NotOnTelegram" = "This user doesn't exist on Telegram."; "Chat.Context.Username.NotOnTelegram" = "This user doesn't exist on Telegram.";
"Chat.Context.Hashtag.Search" = "View Profile"; "Chat.Context.Hashtag.Search" = "Search";
"Chat.Context.Hashtag.Copy" = "Copy Hashtag"; "Chat.Context.Hashtag.Copy" = "Copy Hashtag";
"Chat.Context.Card.Copy" = "Copy Card Number"; "Chat.Context.Card.Copy" = "Copy Card Number";
"Chat.Context.Command.Copy" = "Copy Command";
"Chat.Context.Timecode.Copy" = "Copy Timecode";
"Message.FactCheck" = "Fact Check"; "Message.FactCheck" = "Fact Check";
"Message.FactCheck.WhatIsThis" = "what's this?"; "Message.FactCheck.WhatIsThis" = "what's this?";

View File

@ -1066,6 +1066,9 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
if case let .extracted(extracted) = self.source { if case let .extracted(extracted) = self.source {
if extracted.adjustContentHorizontally { if extracted.adjustContentHorizontally {
contentFrame.origin.x = combinedActionsFrame.minX contentFrame.origin.x = combinedActionsFrame.minX
if contentFrame.maxX > layout.size.width {
contentFrame.origin.x = layout.size.width - contentFrame.width - actionsEdgeInset
}
} }
if extracted.centerVertically { if extracted.centerVertically {
if combinedActionsFrame.height.isZero { if combinedActionsFrame.height.isZero {

View File

@ -225,7 +225,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
let titleColor = bubbleVariableColor(variableColor: messageTheme.actionButtonsTextColor, wallpaper: theme.wallpaper) let titleColor = bubbleVariableColor(variableColor: messageTheme.actionButtonsTextColor, wallpaper: theme.wallpaper)
let attributedTitle: NSAttributedString let attributedTitle: NSAttributedString
if isStarsPayment { if isStarsPayment {
let updatedTitle = title.replacingOccurrences(of: "⭐️", with: " # ") let updatedTitle = title.replacingOccurrences(of: "⭐️", with: " # ")
let buttonAttributedString = NSMutableAttributedString(string: updatedTitle, font: titleFont, textColor: titleColor, paragraphAlignment: .center) let buttonAttributedString = NSMutableAttributedString(string: updatedTitle, font: titleFont, textColor: titleColor, paragraphAlignment: .center)
if let range = buttonAttributedString.string.range(of: "#"), let starImage = UIImage(bundleImageName: "Item List/PremiumIcon") { if let range = buttonAttributedString.string.range(of: "#"), let starImage = UIImage(bundleImageName: "Item List/PremiumIcon") {
buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string)) buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string))

View File

@ -163,11 +163,13 @@ public struct ChatMessageBubbleContentTapAction {
} }
public var content: Content public var content: Content
public var rects: [CGRect]?
public var hasLongTapAction: Bool public var hasLongTapAction: Bool
public var activate: (() -> Promise<Bool>?)? public var activate: (() -> Promise<Bool>?)?
public init(content: Content, hasLongTapAction: Bool = true, activate: (() -> Promise<Bool>?)? = nil) { public init(content: Content, rects: [CGRect]? = nil, hasLongTapAction: Bool = true, activate: (() -> Promise<Bool>?)? = nil) {
self.content = content self.content = content
self.rects = rects
self.hasLongTapAction = hasLongTapAction self.hasLongTapAction = hasLongTapAction
self.activate = activate self.activate = activate
} }

View File

@ -4649,6 +4649,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
let convertedLocation = self.view.convert(location, to: contentNode.view) let convertedLocation = self.view.convert(location, to: contentNode.view)
let tapAction = contentNode.tapActionAtPoint(convertedLocation, gesture: gesture, isEstimating: false) let tapAction = contentNode.tapActionAtPoint(convertedLocation, gesture: gesture, isEstimating: false)
var rects: [CGRect] = []
if let actionRects = tapAction.rects {
for rect in actionRects {
rects.append(rect.offsetBy(dx: contentNode.frame.minX, dy: contentNode.frame.minY))
}
}
switch tapAction.content { switch tapAction.content {
case .none: 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 { 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 {
@ -4692,13 +4699,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
} }
case let .phone(number): case let .phone(number):
return .action(InternalBubbleTapAction.Action({ [weak self] in return .action(InternalBubbleTapAction.Action({ [weak self] in
guard let self, let item = self.item, let contentNode = self.contextContentNodeForLink(number) else { guard let self, let item = self.item, let contentNode = self.contextContentNodeForLink(number, rects: rects) else {
return return
} }
item.controllerInteraction.longTap(.phone(number), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) item.controllerInteraction.longTap(.phone(number), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
// item.controllerInteraction.openPhoneContextMenu(ChatControllerInteraction.OpenPhone(number: number, message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
}, contextMenuOnLongPress: !tapAction.hasLongTapAction)) }, contextMenuOnLongPress: !tapAction.hasLongTapAction))
case let .peerMention(peerId, _, openProfile): case let .peerMention(peerId, _, openProfile):
return .action(InternalBubbleTapAction.Action { [weak self] in return .action(InternalBubbleTapAction.Action { [weak self] in
@ -4768,7 +4773,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
case let .bankCard(number): case let .bankCard(number):
if let item = self.item { if let item = self.item {
return .action(InternalBubbleTapAction.Action { [weak self] in return .action(InternalBubbleTapAction.Action { [weak self] in
guard let self, let contentNode = self.contextContentNodeForLink(number) else { guard let self, let contentNode = self.contextContentNodeForLink(number, rects: rects) else {
return return
} }
item.controllerInteraction.longTap(.bankCard(number), ChatControllerInteraction.LongTapParams(message: item.message, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) item.controllerInteraction.longTap(.bankCard(number), ChatControllerInteraction.LongTapParams(message: item.message, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
@ -4854,6 +4859,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
tapMessage = contentNode.item?.message tapMessage = contentNode.item?.message
} }
let tapAction = contentNode.tapActionAtPoint(convertedLocation, gesture: gesture, isEstimating: false) let tapAction = contentNode.tapActionAtPoint(convertedLocation, gesture: gesture, isEstimating: false)
var rects: [CGRect] = []
if let actionRects = tapAction.rects {
for rect in actionRects {
rects.append(rect.offsetBy(dx: contentNode.frame.minX, dy: contentNode.frame.minY))
}
}
switch tapAction.content { switch tapAction.content {
case .none, .ignore: case .none, .ignore:
break break
@ -4861,7 +4873,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
if tapAction.hasLongTapAction { if tapAction.hasLongTapAction {
return .action(InternalBubbleTapAction.Action({ [weak self] in return .action(InternalBubbleTapAction.Action({ [weak self] in
let cleanUrl = url.url.replacingOccurrences(of: "mailto:", with: "") let cleanUrl = url.url.replacingOccurrences(of: "mailto:", with: "")
guard let self, let contentNode = self.contextContentNodeForLink(cleanUrl) else { guard let self, let contentNode = self.contextContentNodeForLink(cleanUrl, rects: rects) else {
return return
} }
item.controllerInteraction.longTap(.url(url.url), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) item.controllerInteraction.longTap(.url(url.url), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
@ -4871,35 +4883,35 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
} }
case let .phone(number): case let .phone(number):
return .action(InternalBubbleTapAction.Action({ [weak self] in return .action(InternalBubbleTapAction.Action({ [weak self] in
guard let self, let contentNode = self.contextContentNodeForLink(number) else { guard let self, let contentNode = self.contextContentNodeForLink(number, rects: rects) else {
return return
} }
item.controllerInteraction.longTap(.phone(number), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) item.controllerInteraction.longTap(.phone(number), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
}, contextMenuOnLongPress: !tapAction.hasLongTapAction)) }, contextMenuOnLongPress: !tapAction.hasLongTapAction))
case let .peerMention(peerId, mention, _): case let .peerMention(peerId, mention, _):
return .action(InternalBubbleTapAction.Action { [weak self] in return .action(InternalBubbleTapAction.Action { [weak self] in
guard let self, let contentNode = self.contextContentNodeForLink(mention) else { guard let self, let contentNode = self.contextContentNodeForLink(mention, rects: rects) else {
return return
} }
item.controllerInteraction.longTap(.peerMention(peerId, mention), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) item.controllerInteraction.longTap(.peerMention(peerId, mention), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
}) })
case let .textMention(name): case let .textMention(name):
return .action(InternalBubbleTapAction.Action { [weak self] in return .action(InternalBubbleTapAction.Action { [weak self] in
guard let self, let contentNode = self.contextContentNodeForLink(name) else { guard let self, let contentNode = self.contextContentNodeForLink(name, rects: rects) else {
return return
} }
item.controllerInteraction.longTap(.mention(name), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) item.controllerInteraction.longTap(.mention(name), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
}) })
case let .botCommand(command): case let .botCommand(command):
return .action(InternalBubbleTapAction.Action { [weak self] in return .action(InternalBubbleTapAction.Action { [weak self] in
guard let self, let contentNode = self.contextContentNodeForLink(command) else { guard let self, let contentNode = self.contextContentNodeForLink(command, rects: rects) else {
return return
} }
item.controllerInteraction.longTap(.command(command), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) item.controllerInteraction.longTap(.command(command), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
}) })
case let .hashtag(_, hashtag): case let .hashtag(_, hashtag):
return .action(InternalBubbleTapAction.Action { [weak self] in return .action(InternalBubbleTapAction.Action { [weak self] in
guard let self, let contentNode = self.contextContentNodeForLink(hashtag) else { guard let self, let contentNode = self.contextContentNodeForLink(hashtag, rects: rects) else {
return return
} }
item.controllerInteraction.longTap(.hashtag(hashtag), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) item.controllerInteraction.longTap(.hashtag(hashtag), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
@ -4917,7 +4929,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
case let .timecode(timecode, text): case let .timecode(timecode, text):
if let mediaMessage = mediaMessage { if let mediaMessage = mediaMessage {
return .action(InternalBubbleTapAction.Action { [weak self] in return .action(InternalBubbleTapAction.Action { [weak self] in
guard let self, let contentNode = self.contextContentNodeForLink(text) else { guard let self, let contentNode = self.contextContentNodeForLink(text, rects: rects) else {
return return
} }
item.controllerInteraction.longTap(.timecode(timecode, text), ChatControllerInteraction.LongTapParams(message: mediaMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) item.controllerInteraction.longTap(.timecode(timecode, text), ChatControllerInteraction.LongTapParams(message: mediaMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
@ -4925,7 +4937,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
} }
case let .bankCard(number): case let .bankCard(number):
return .action(InternalBubbleTapAction.Action { [weak self] in return .action(InternalBubbleTapAction.Action { [weak self] in
guard let self, let contentNode = self.contextContentNodeForLink(number) else { guard let self, let contentNode = self.contextContentNodeForLink(number, rects: rects) else {
return return
} }
item.controllerInteraction.longTap(.bankCard(number), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) item.controllerInteraction.longTap(.bankCard(number), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
@ -4963,7 +4975,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
return nil return nil
} }
private func contextContentNodeForLink(_ link: String) -> ContextExtractedContentContainingNode? { private func contextContentNodeForLink(_ link: String, rects: [CGRect]?) -> ContextExtractedContentContainingNode? {
guard let item = self.item else { guard let item = self.item else {
return nil return nil
} }
@ -4972,8 +4984,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
let incoming = item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) let incoming = item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData)
let textNode = ImmediateTextNode() let textNode = ImmediateTextNode()
textNode.maximumNumberOfLines = 2
textNode.attributedText = NSAttributedString(string: link, font: Font.regular(item.presentationData.fontSize.baseDisplaySize), textColor: incoming ? item.presentationData.theme.theme.chat.message.incoming.linkTextColor : item.presentationData.theme.theme.chat.message.outgoing.linkTextColor) textNode.attributedText = NSAttributedString(string: link, font: Font.regular(item.presentationData.fontSize.baseDisplaySize), textColor: incoming ? item.presentationData.theme.theme.chat.message.incoming.linkTextColor : item.presentationData.theme.theme.chat.message.outgoing.linkTextColor)
let textSize = textNode.updateLayout(CGSize(width: 1000.0, height: 100.0)) let textSize = textNode.updateLayout(CGSize(width: self.bounds.width - 32.0, height: 100.0))
let backgroundNode = ASDisplayNode() let backgroundNode = ASDisplayNode()
backgroundNode.backgroundColor = (incoming ? item.presentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper.fill : item.presentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper.fill).first ?? .black backgroundNode.backgroundColor = (incoming ? item.presentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper.fill : item.presentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper.fill).first ?? .black
@ -4986,7 +4999,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
textNode.frame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: textSize) textNode.frame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: textSize)
backgroundNode.addSubnode(textNode) backgroundNode.addSubnode(textNode)
containingNode.frame = CGRect(origin: CGPoint(x: self.backgroundNode.frame.minX + 3.0, y: 1.0), size: CGSize(width: backgroundSize.width, height: backgroundSize.height + 20.0)) var origin = CGPoint(x: self.backgroundNode.frame.minX + 3.0, y: 1.0)
if let rect = rects?.first {
origin = rect.origin
}
containingNode.frame = CGRect(origin: origin, size: CGSize(width: backgroundSize.width, height: backgroundSize.height + 20.0))
containingNode.contentNode.frame = CGRect(origin: .zero, size: backgroundSize) containingNode.contentNode.frame = CGRect(origin: .zero, size: backgroundSize)
containingNode.contentRect = CGRect(origin: .zero, size: backgroundSize) containingNode.contentRect = CGRect(origin: .zero, size: backgroundSize)
containingNode.contentNode.addSubnode(backgroundNode) containingNode.contentNode.addSubnode(backgroundNode)

View File

@ -929,6 +929,24 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
let textNodeFrame = self.textNode.textNode.frame let textNodeFrame = self.textNode.textNode.frame
let textLocalPoint = CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY) let textLocalPoint = CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)
if let (index, attributes) = self.textNode.textNode.attributesAtPoint(textLocalPoint) { if let (index, attributes) = self.textNode.textNode.attributesAtPoint(textLocalPoint) {
var rects: [CGRect]?
let possibleNames: [String] = [
TelegramTextAttributes.URL,
TelegramTextAttributes.PeerMention,
TelegramTextAttributes.PeerTextMention,
TelegramTextAttributes.BotCommand,
TelegramTextAttributes.Hashtag,
TelegramTextAttributes.Timecode,
TelegramTextAttributes.BankCard
]
for name in possibleNames {
if let _ = attributes[NSAttributedString.Key(rawValue: name)] {
rects = self.textNode.textNode.attributeRects(name: name, at: index)
break
}
}
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !self.displayContentsUnderSpoilers.value { if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !self.displayContentsUnderSpoilers.value {
return ChatMessageBubbleContentTapAction(content: .none) return ChatMessageBubbleContentTapAction(content: .none)
} else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { } else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
@ -946,28 +964,28 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
content = .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: concealed)) content = .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: concealed))
} }
return ChatMessageBubbleContentTapAction(content: content, activate: makeActivate(urlRange)) return ChatMessageBubbleContentTapAction(content: content, rects: rects, activate: makeActivate(urlRange))
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention { } else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
return ChatMessageBubbleContentTapAction(content: .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)) return ChatMessageBubbleContentTapAction(content: .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false), rects: rects)
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { } else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
var urlRange: NSRange? var urlRange: NSRange?
if let (_, _, urlRangeValue) = self.textNode.textNode.attributeSubstringWithRange(name: TelegramTextAttributes.PeerTextMention, index: index) { if let (_, _, urlRangeValue) = self.textNode.textNode.attributeSubstringWithRange(name: TelegramTextAttributes.PeerTextMention, index: index) {
urlRange = urlRangeValue urlRange = urlRangeValue
} }
return ChatMessageBubbleContentTapAction(content: .textMention(peerName), activate: makeActivate(urlRange)) return ChatMessageBubbleContentTapAction(content: .textMention(peerName), rects: rects, activate: makeActivate(urlRange))
} else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String { } else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {
return ChatMessageBubbleContentTapAction(content: .botCommand(botCommand)) return ChatMessageBubbleContentTapAction(content: .botCommand(botCommand), rects: rects)
} else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag { } else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
return ChatMessageBubbleContentTapAction(content: .hashtag(hashtag.peerName, hashtag.hashtag)) return ChatMessageBubbleContentTapAction(content: .hashtag(hashtag.peerName, hashtag.hashtag), rects: rects)
} else if let timecode = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Timecode)] as? TelegramTimecode { } else if let timecode = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Timecode)] as? TelegramTimecode {
return ChatMessageBubbleContentTapAction(content: .timecode(timecode.time, timecode.text)) return ChatMessageBubbleContentTapAction(content: .timecode(timecode.time, timecode.text), rects: rects)
} else if let bankCard = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BankCard)] as? String { } else if let bankCard = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BankCard)] as? String {
var urlRange: NSRange? var urlRange: NSRange?
if let (_, _, urlRangeValue) = self.textNode.textNode.attributeSubstringWithRange(name: TelegramTextAttributes.BankCard, index: index) { if let (_, _, urlRangeValue) = self.textNode.textNode.attributeSubstringWithRange(name: TelegramTextAttributes.BankCard, index: index) {
urlRange = urlRangeValue urlRange = urlRangeValue
} }
return ChatMessageBubbleContentTapAction(content: .bankCard(bankCard), activate: makeActivate(urlRange)) return ChatMessageBubbleContentTapAction(content: .bankCard(bankCard), rects: rects, activate: makeActivate(urlRange))
} else if let pre = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Pre)] as? String { } else if let pre = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Pre)] as? String {
return ChatMessageBubbleContentTapAction(content: .copy(pre)) return ChatMessageBubbleContentTapAction(content: .copy(pre))
} else if let code = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Code)] as? String { } else if let code = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Code)] as? String {

View File

@ -69,6 +69,7 @@ public enum CodableDrawingEntity: Equatable {
var size: CGSize? var size: CGSize?
var rotation: CGFloat? var rotation: CGFloat?
var scale: CGFloat? var scale: CGFloat?
var cornerRadius: Double?
switch self { switch self {
case let .location(entity): case let .location(entity):
@ -76,6 +77,9 @@ public enum CodableDrawingEntity: Equatable {
size = entity.renderImage?.size size = entity.renderImage?.size
rotation = entity.rotation rotation = entity.rotation
scale = entity.scale scale = entity.scale
if let size {
cornerRadius = 10.0 / (size.width * entity.scale)
}
case let .sticker(entity): case let .sticker(entity):
var entityPosition = entity.position var entityPosition = entity.position
var entitySize = entity.baseSize var entitySize = entity.baseSize
@ -83,7 +87,7 @@ public enum CodableDrawingEntity: Equatable {
let entityScale = entity.scale let entityScale = entity.scale
if case .message = entity.content { if case .message = entity.content {
let offset: CGFloat = 16.18 * entityScale //54.0 * entityScale / 3.337 let offset: CGFloat = 16.18 * entityScale
entitySize = CGSize(width: entitySize.width - 38.0, height: entitySize.height - 4.0) entitySize = CGSize(width: entitySize.width - 38.0, height: entitySize.height - 4.0)
entityPosition = CGPoint(x: entityPosition.x + offset * cos(entityRotation), y: entityPosition.y + offset * sin(entityRotation)) entityPosition = CGPoint(x: entityPosition.x + offset * cos(entityRotation), y: entityPosition.y + offset * sin(entityRotation))
} }
@ -94,9 +98,17 @@ public enum CodableDrawingEntity: Equatable {
scale = entityScale scale = entityScale
case let .link(entity): case let .link(entity):
position = entity.position position = entity.position
size = entity.renderImage?.size
rotation = entity.rotation rotation = entity.rotation
scale = entity.scale scale = entity.scale
if let entitySize = entity.renderImage?.size {
if entity.whiteImage != nil {
cornerRadius = 38.0 / (entitySize.width * entity.scale)
size = CGSize(width: entitySize.width - 28.0, height: entitySize.height - 26.0)
} else {
cornerRadius = 10.0 / (entitySize.width * entity.scale)
size = entitySize
}
}
default: default:
return nil return nil
} }
@ -105,13 +117,16 @@ public enum CodableDrawingEntity: Equatable {
return nil return nil
} }
let width = size.width * scale / 1080.0 * 100.0
let height = size.height * scale / 1920.0 * 100.0
return MediaArea.Coordinates( return MediaArea.Coordinates(
x: position.x / 1080.0 * 100.0, x: position.x / 1080.0 * 100.0,
y: position.y / 1920.0 * 100.0, y: position.y / 1920.0 * 100.0,
width: size.width * scale / 1080.0 * 100.0, width: width,
height: size.height * scale / 1920.0 * 100.0, height: height,
rotation: rotation / .pi * 180.0, rotation: rotation / .pi * 180.0,
cornerRadius: nil cornerRadius: cornerRadius.flatMap { $0 * 100.0 }
) )
} }

View File

@ -0,0 +1,69 @@
import Foundation
import UIKit
import SwiftSignalKit
import Postbox
import TelegramCore
import AsyncDisplayKit
import Display
import ContextUI
import UndoUI
import AccountContext
import ChatMessageItemView
import ChatMessageItemCommon
import ChatControllerInteraction
extension ChatControllerImpl {
func openCommandContextMenu(command: String, params: ChatControllerInteraction.LongTapParams) -> Void {
guard let message = params.message, let contentNode = params.contentNode else {
return
}
guard let messages = self.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(message.id) else {
return
}
var updatedMessages = messages
for i in 0 ..< updatedMessages.count {
if updatedMessages[i].id == message.id {
let message = updatedMessages.remove(at: i)
updatedMessages.insert(message, at: 0)
break
}
}
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture
let source: ContextContentSource
// if let location = location {
// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y)))
// } else {
source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
// }
var items: [ContextMenuItem] = []
items.append(
.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Command_Copy, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
guard let self else {
return
}
UIPasteboard.general.string = command
self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
}))
)
self.canReadHistory.set(false)
let controller = ContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))), recognizer: recognizer, gesture: gesture, disableScreenshots: false)
controller.dismissed = { [weak self] in
self?.canReadHistory.set(true)
}
self.window?.presentInGlobalOverlay(controller)
}
}

View File

@ -26,16 +26,11 @@ extension ChatControllerImpl {
case let .peerMention(peerId, mention): case let .peerMention(peerId, mention):
self.openMentionContextMenu(username: mention, peerId: peerId, params: params) self.openMentionContextMenu(username: mention, peerId: peerId, params: params)
case let .command(command): case let .command(command):
let _ = command self.openCommandContextMenu(command: command, params: params)
break
// self.openBotCommandContextMenu(command: command, params: params)
case let .hashtag(hashtag): case let .hashtag(hashtag):
self.openHashtagContextMenu(hashtag: hashtag, params: params) self.openHashtagContextMenu(hashtag: hashtag, params: params)
case let .timecode(value, timecode): case let .timecode(value, timecode):
let _ = value self.openTimecodeContextMenu(timecode: timecode, value: value, params: params)
let _ = timecode
break
// self.openTimecodeContextMenu(timecode: timecode, params: params)
case let .bankCard(number): case let .bankCard(number):
self.openBankCardContextMenu(number: number, params: params) self.openBankCardContextMenu(number: number, params: params)
case let .phone(number): case let .phone(number):

View File

@ -0,0 +1,69 @@
import Foundation
import UIKit
import SwiftSignalKit
import Postbox
import TelegramCore
import AsyncDisplayKit
import Display
import ContextUI
import UndoUI
import AccountContext
import ChatMessageItemView
import ChatMessageItemCommon
import ChatControllerInteraction
extension ChatControllerImpl {
func openTimecodeContextMenu(timecode: String, value: Double, params: ChatControllerInteraction.LongTapParams) -> Void {
guard let message = params.message, let contentNode = params.contentNode else {
return
}
guard let messages = self.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(message.id) else {
return
}
var updatedMessages = messages
for i in 0 ..< updatedMessages.count {
if updatedMessages[i].id == message.id {
let message = updatedMessages.remove(at: i)
updatedMessages.insert(message, at: 0)
break
}
}
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture
let source: ContextContentSource
// if let location = location {
// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y)))
// } else {
source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
// }
var items: [ContextMenuItem] = []
items.append(
.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Context_Timecode_Copy, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
guard let self else {
return
}
UIPasteboard.general.string = timecode
self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
}))
)
self.canReadHistory.set(false)
let controller = ContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))), recognizer: recognizer, gesture: gesture, disableScreenshots: false)
controller.dismissed = { [weak self] in
self?.canReadHistory.set(true)
}
self.window?.presentInGlobalOverlay(controller)
}
}

View File

@ -322,10 +322,15 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
messageUpdated = true messageUpdated = true
} }
var isStarsPayment = false
if let message = interfaceState.pinnedMessage, !message.message.isRestricted(platform: "ios", contentSettings: self.context.currentContentSettings.with { $0 }) { if let message = interfaceState.pinnedMessage, !message.message.isRestricted(platform: "ios", contentSettings: self.context.currentContentSettings.with { $0 }) {
for attribute in message.message.attributes { for attribute in message.message.attributes {
if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), attribute.rows.count == 1, attribute.rows[0].buttons.count == 1 { if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), attribute.rows.count == 1, attribute.rows[0].buttons.count == 1 {
actionTitle = attribute.rows[0].buttons[0].title let title = attribute.rows[0].buttons[0].title
actionTitle = title
if case .payment = attribute.rows[0].buttons[0].action, title.contains("⭐️") {
isStarsPayment = true
}
} }
} }
} else { } else {
@ -430,7 +435,20 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
self.actionButtonBackgroundNode.isHidden = false self.actionButtonBackgroundNode.isHidden = false
self.actionButtonTitleNode.isHidden = false self.actionButtonTitleNode.isHidden = false
self.actionButtonTitleNode.attributedText = NSAttributedString(string: actionTitle, font: Font.with(size: 15.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: interfaceState.theme.list.itemCheckColors.foregroundColor) let attributedTitle: NSAttributedString
if isStarsPayment {
let updatedTitle = actionTitle.replacingOccurrences(of: "⭐️", with: " # ")
let buttonAttributedString = NSMutableAttributedString(string: updatedTitle, font: Font.with(size: 15.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: interfaceState.theme.list.itemCheckColors.foregroundColor)
if let range = buttonAttributedString.string.range(of: "#"), let starImage = UIImage(bundleImageName: "Item List/PremiumIcon") {
buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string))
buttonAttributedString.addAttribute(.foregroundColor, value: interfaceState.theme.list.itemCheckColors.foregroundColor, range: NSRange(range, in: buttonAttributedString.string))
buttonAttributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: buttonAttributedString.string))
}
attributedTitle = buttonAttributedString
} else {
attributedTitle = NSAttributedString(string: actionTitle, font: Font.with(size: 15.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: interfaceState.theme.list.itemCheckColors.foregroundColor)
}
self.actionButtonTitleNode.attributedText = attributedTitle
let actionButtonTitleSize = self.actionButtonTitleNode.updateLayout(CGSize(width: 150.0, height: .greatestFiniteMagnitude)) let actionButtonTitleSize = self.actionButtonTitleNode.updateLayout(CGSize(width: 150.0, height: .greatestFiniteMagnitude))
let actionButtonSize = CGSize(width: max(actionButtonTitleSize.width + 20.0, 40.0), height: 28.0) let actionButtonSize = CGSize(width: max(actionButtonTitleSize.width + 20.0, 40.0), height: 28.0)