Various improvements

This commit is contained in:
Ali 2023-10-22 22:06:14 +04:00
parent 00455e1163
commit ab5e3ae947
23 changed files with 335 additions and 145 deletions

View File

@ -985,6 +985,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
private var cachedChatListText: (String, String)?
private var cachedChatListSearchResult: CachedChatListSearchResult?
private var cachedChatListQuoteSearchResult: CachedChatListSearchResult?
private var cachedCustomTextEntities: CachedCustomTextEntities?
var layoutParams: (ChatListItem, first: Bool, last: Bool, firstWithHeader: Bool, nextIsPinned: Bool, ListViewItemLayoutParams, countersSize: CGFloat)?
@ -1585,6 +1586,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let currentItem = self.layoutParams?.0
let currentChatListText = self.cachedChatListText
let currentChatListSearchResult = self.cachedChatListSearchResult
let currentChatListQuoteSearchResult = self.cachedChatListQuoteSearchResult
let currentCustomTextEntities = self.cachedCustomTextEntities
return { item, params, first, last, firstWithHeader, nextIsPinned in
@ -1869,6 +1871,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var chatListText: (String, String)?
var chatListSearchResult: CachedChatListSearchResult?
var chatListQuoteSearchResult: CachedChatListSearchResult?
var customTextEntities: CachedCustomTextEntities?
let contentImageSide: CGFloat = max(10.0, min(20.0, floor(item.presentationData.fontSize.baseDisplaySize * 18.0 / 17.0)))
@ -2016,15 +2019,45 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
composedString = NSMutableAttributedString(attributedString: messageString)
}
var composedReplyString: NSMutableAttributedString?
if let searchQuery = item.interaction.searchTextHighightState {
var quoteText: String?
for attribute in message.attributes {
if let attribute = attribute as? ReplyMessageAttribute {
if let quote = attribute.quote {
quoteText = quote.text
}
} else if let attribute = attribute as? QuotedReplyMessageAttribute {
if let quote = attribute.quote {
quoteText = quote.text
}
}
}
if let quoteText {
let quoteString = foldLineBreaks(stringWithAppliedEntities(quoteText, entities: [], baseColor: theme.messageTextColor, linkColor: theme.messageTextColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: italicTextFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: nil))
composedReplyString = NSMutableAttributedString(attributedString: quoteString)
}
if let cached = currentChatListSearchResult, cached.matches(text: composedString.string, searchQuery: searchQuery) {
chatListSearchResult = cached
} else {
let (ranges, text) = findSubstringRanges(in: composedString.string, query: searchQuery)
chatListSearchResult = CachedChatListSearchResult(text: text, searchQuery: searchQuery, resultRanges: ranges)
}
if let composedReplyString {
if let cached = currentChatListQuoteSearchResult, cached.matches(text: composedReplyString.string, searchQuery: searchQuery) {
chatListQuoteSearchResult = cached
} else {
let (ranges, text) = findSubstringRanges(in: composedReplyString.string, query: searchQuery)
chatListQuoteSearchResult = CachedChatListSearchResult(text: text, searchQuery: searchQuery, resultRanges: ranges)
}
} else {
chatListQuoteSearchResult = nil
}
} else {
chatListSearchResult = nil
chatListQuoteSearchResult = nil
}
if let chatListSearchResult = chatListSearchResult, let firstRange = chatListSearchResult.resultRanges.first {
@ -2053,6 +2086,34 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
composedString = composedString.attributedSubstring(from: NSMakeRange(leftOrigin, composedString.length - leftOrigin)).mutableCopy() as! NSMutableAttributedString
composedString.insert(NSAttributedString(string: "\u{2026}", attributes: [NSAttributedString.Key.font: textFont, NSAttributedString.Key.foregroundColor: theme.messageTextColor]), at: 0)
}
} else if var composedReplyString, let chatListQuoteSearchResult, let firstRange = chatListQuoteSearchResult.resultRanges.first {
for range in chatListQuoteSearchResult.resultRanges {
let stringRange = NSRange(range, in: chatListQuoteSearchResult.text)
if stringRange.location >= 0 && stringRange.location + stringRange.length <= composedReplyString.length {
var stringRange = stringRange
if stringRange.location > composedReplyString.length {
continue
} else if stringRange.location + stringRange.length > composedReplyString.length {
stringRange.length = composedReplyString.length - stringRange.location
}
composedReplyString.addAttribute(.foregroundColor, value: theme.messageHighlightedTextColor, range: stringRange)
}
}
let firstRangeOrigin = chatListQuoteSearchResult.text.distance(from: chatListQuoteSearchResult.text.startIndex, to: firstRange.lowerBound)
if firstRangeOrigin > 24 {
var leftOrigin: Int = 0
(composedReplyString.string as NSString).enumerateSubstrings(in: NSMakeRange(0, firstRangeOrigin), options: [.byWords, .reverse]) { (str, range1, _, _) in
let distanceFromEnd = firstRangeOrigin - range1.location
if (distanceFromEnd > 12 || range1.location == 0) && leftOrigin == 0 {
leftOrigin = range1.location
}
}
composedReplyString = composedReplyString.attributedSubstring(from: NSMakeRange(leftOrigin, composedReplyString.length - leftOrigin)).mutableCopy() as! NSMutableAttributedString
composedReplyString.insert(NSAttributedString(string: "\u{2026}", attributes: [NSAttributedString.Key.font: textFont, NSAttributedString.Key.foregroundColor: theme.messageTextColor]), at: 0)
}
composedString = composedReplyString
}
attributedText = composedString
@ -2712,6 +2773,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
strongSelf.currentItemHeight = itemHeight
strongSelf.cachedChatListText = chatListText
strongSelf.cachedChatListSearchResult = chatListSearchResult
strongSelf.cachedChatListQuoteSearchResult = chatListQuoteSearchResult
strongSelf.cachedCustomTextEntities = customTextEntities
strongSelf.onlineIsVoiceChat = onlineIsVoiceChat

View File

@ -738,14 +738,21 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
var textFrame = self.textFieldFrame
textFrame.origin = CGPoint(x: 13.0, y: 6.0 - UIScreenPixel)
textFrame.size.height = self.textInputView.contentSize.height
textFrame.size.width -= self.textInputView.textContainerInset.right
if let textInputView = self.textInputView as? ChatInputTextView {
textFrame.size.width -= textInputView.defaultTextContainerInset.right
} else {
textFrame.size.width -= self.textInputView.textContainerInset.right
}
if self.textInputView.isRTL {
textFrame.origin.x -= messageOriginDelta
}
self.fromMessageTextNode.frame = textFrame
self.fromMessageTextNode.updateLayout(size: textFrame.size)
self.toMessageTextNode.frame = textFrame
self.toMessageTextNode.updateLayout(size: textFrame.size)
}
@objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) {

View File

@ -738,7 +738,11 @@ public extension ContainedViewLayoutTransition {
case .immediate:
layer.removeAnimation(forKey: "position")
layer.removeAnimation(forKey: "bounds")
layer.frame = frame
if let view = layer.delegate as? UIView {
view.frame = frame
} else {
layer.frame = frame
}
if let completion = completion {
completion(true)
}

View File

@ -184,6 +184,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
case chatReplyOptionsTip = 50
case displayStoryInteractionGuide = 51
case dismissedPremiumAppIconsBadge = 52
case replyQuoteTextSelectionTip = 53
var key: ValueBoxKey {
let v = ValueBoxKey(length: 4)
@ -449,6 +450,10 @@ private struct ApplicationSpecificNoticeKeys {
static func dismissedPremiumAppIconsBadge() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedPremiumAppIconsBadge.key)
}
static func replyQuoteTextSelectionTip() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.replyQuoteTextSelectionTip.key)
}
}
public struct ApplicationSpecificNotice {
@ -924,6 +929,30 @@ public struct ApplicationSpecificNotice {
}
}
}
public static func getReplyQuoteTextSelectionTips(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> {
return accountManager.transaction { transaction -> Int32 in
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.replyQuoteTextSelectionTip())?.get(ApplicationSpecificCounterNotice.self) {
return value.value
} else {
return 0
}
}
}
public static func incrementReplyQuoteTextSelectionTips(accountManager: AccountManager<TelegramAccountManagerTypes>, count: Int32 = 1) -> Signal<Void, NoError> {
return accountManager.transaction { transaction -> Void in
var currentValue: Int32 = 0
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.replyQuoteTextSelectionTip())?.get(ApplicationSpecificCounterNotice.self) {
currentValue = value.value
}
currentValue += count
if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) {
transaction.setNotice(ApplicationSpecificNoticeKeys.replyQuoteTextSelectionTip(), entry)
}
}
}
public static func getMessageViewsPrivacyTips(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> {
return accountManager.transaction { transaction -> Int32 in

View File

@ -1488,8 +1488,10 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
}
var replyBackgroundFrame: CGRect?
if let (replyInfoSize, replyInfoApply) = replyInfoApply {
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: replyInfoSize)
replyBackgroundFrame = replyInfoFrame
let replyInfoNode = replyInfoApply(replyInfoFrame.size, synchronousLoads, animation)
if strongSelf.replyInfoNode == nil {
@ -1504,8 +1506,8 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
strongSelf.replyInfoNode = nil
}
if let replyBackgroundNode = strongSelf.replyBackgroundNode {
replyBackgroundNode.frame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)) - 4.0, y: headersOffset + 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0))
if let replyBackgroundNode = strongSelf.replyBackgroundNode, let replyBackgroundFrame {
replyBackgroundNode.frame = replyBackgroundFrame
let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0
replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: cornerRadius, transition: .immediate)
@ -1974,6 +1976,10 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
return .optionalAction({
item.controllerInteraction.navigateToStory(item.message, attribute.storyId)
})
} else if let attribute = attribute as? QuotedReplyMessageAttribute {
return .action({
item.controllerInteraction.attemptedNavigationToPrivateQuote(attribute.peerId.flatMap { item.message.peers[$0] })
})
}
}
}

View File

@ -843,14 +843,14 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
if let current = self.backgroundView {
backgroundView = current
animation.animator.updateFrame(layer: backgroundView.layer, frame: backgroundFrame, completion: nil)
backgroundView.update(size: backgroundFrame.size, primaryColor: mainColor, secondaryColor: secondaryColor, pattern: nil, animation: animation)
} else {
backgroundView = MessageInlineBlockBackgroundView()
self.backgroundView = backgroundView
backgroundView.frame = backgroundFrame
self.transformContainer.view.insertSubview(backgroundView, at: 0)
backgroundView.update(size: backgroundFrame.size, primaryColor: mainColor, secondaryColor: secondaryColor, pattern: nil, animation: .None)
}
backgroundView.update(size: backgroundFrame.size, primaryColor: mainColor, secondaryColor: secondaryColor, pattern: nil, animation: animation)
} else {
if let backgroundView = self.backgroundView {
self.backgroundView = nil

View File

@ -3955,6 +3955,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
return .action({
item.controllerInteraction.navigateToStory(item.message, attribute.storyId)
})
} else if let attribute = attribute as? QuotedReplyMessageAttribute {
return .action({
item.controllerInteraction.attemptedNavigationToPrivateQuote(attribute.peerId.flatMap { item.message.peers[$0] })
})
}
}
}
@ -4159,6 +4163,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
return .action({
item.controllerInteraction.navigateToStory(item.message, attribute.storyId)
})
} else if let attribute = attribute as? QuotedReplyMessageAttribute {
return .action({
item.controllerInteraction.attemptedNavigationToPrivateQuote(attribute.peerId.flatMap { item.message.peers[$0] })
})
}
}
}

View File

@ -761,8 +761,10 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco
}
}
var replyBackgroundFrame: CGRect?
if let (replyInfoSize, replyInfoApply) = replyInfoApply {
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0 + messageInfoSize.height), size: replyInfoSize)
replyBackgroundFrame = replyInfoFrame
let replyInfoNode = replyInfoApply(replyInfoFrame.size, synchronousLoads, animation)
if strongSelf.replyInfoNode == nil {
@ -777,8 +779,8 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco
strongSelf.replyInfoNode = nil
}
if let replyBackgroundNode = strongSelf.replyBackgroundNode {
replyBackgroundNode.frame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)) - 4.0, y: 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0))
if let replyBackgroundNode = strongSelf.replyBackgroundNode, let replyBackgroundFrame {
replyBackgroundNode.frame = replyBackgroundFrame
let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0
replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: cornerRadius, transition: .immediate)
@ -963,6 +965,10 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco
return .optionalAction({
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text))
})
} else if let attribute = attribute as? QuotedReplyMessageAttribute {
return .action({
item.controllerInteraction.attemptedNavigationToPrivateQuote(attribute.peerId.flatMap { item.message.peers[$0] })
})
}
}
}

View File

@ -999,8 +999,10 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
}
}
var replyBackgroundFrame: CGRect?
if let (replyInfoSize, replyInfoApply) = replyInfoApply {
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (displayVideoFrame.maxX - width + 5.0) : (width - messageInfoSize.width - bubbleEdgeInset - 9.0 + 10.0)), y: 8.0 + messageInfoSize.height), size: replyInfoSize)
replyBackgroundFrame = replyInfoFrame
let replyInfoNode = replyInfoApply(replyInfoFrame.size, false, animation)
if strongSelf.replyInfoNode == nil {
@ -1015,8 +1017,8 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
strongSelf.replyInfoNode = nil
}
if let replyBackgroundNode = strongSelf.replyBackgroundNode {
let replyBackgroundFrame = CGRect(origin: CGPoint(x: (!incoming ? (displayVideoFrame.maxX - width + 4.0) : (width - messageInfoSize.width - bubbleEdgeInset)) - 4.0, y: 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0))
if let replyBackgroundNode = strongSelf.replyBackgroundNode, let replyBackgroundFrame {
let replyBackgroundFrame = replyBackgroundFrame
animation.animator.updateFrame(layer: replyBackgroundNode.layer, frame: replyBackgroundFrame, completion: nil)
let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0
@ -1294,6 +1296,9 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
} else if let attribute = attribute as? ReplyStoryAttribute {
item.controllerInteraction.navigateToStory(item.message, attribute.storyId)
return
} else if let attribute = attribute as? QuotedReplyMessageAttribute {
item.controllerInteraction.attemptedNavigationToPrivateQuote(attribute.peerId.flatMap { item.message.peers[$0] })
return
}
}
}

View File

@ -101,7 +101,6 @@ public enum ChatMessageMerge: Int32 {
public protocol ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
func updateSelectionState(animated: Bool)
func updateSublayerTransformOffset(layer: CALayer, offset: CGPoint)
}
public protocol ChatMessageItem: ListViewItem {

View File

@ -436,7 +436,7 @@ private let avatarFont = avatarPlaceholderFont(size: 16.0)
private let maxVideoLoopCount = 3
public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode {
public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, ChatMessageAvatarHeaderNode {
private let context: AccountContext
private var presentationData: ChatPresentationData
private let controllerInteraction: ChatControllerInteraction

View File

@ -215,6 +215,9 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
case .standalone:
let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper)
titleColor = serviceColor.primaryText
if dashSecondaryColor != nil {
secondaryColor = .clear
}
mainColor = serviceMessageColorComponents(chatTheme: arguments.presentationData.theme.theme.chat, wallpaper: arguments.presentationData.theme.wallpaper).primaryText
dustColor = titleColor
@ -457,7 +460,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
imageDimensions = representation.dimensions.cgSize
}
break
} else if let file = media as? TelegramMediaFile, file.isVideo && !file.isVideoSticker {
} else if let file = media as? TelegramMediaFile, !file.isVideoSticker {
updatedMediaReference = .message(message: MessageReference(message), media: file)
if let dimensions = file.dimensions {
@ -527,14 +530,12 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
var textCutoutWidth: CGFloat = 0.0
if arguments.quote != nil || arguments.replyForward?.quote != nil {
additionalTitleWidth += 10.0
if case .bubble = arguments.type {
maxTitleNumberOfLines = 2
maxTextNumberOfLines = 5
if imageTextInset != 0.0 {
adjustedConstrainedTextSize.width += imageTextInset
textCutout = TextNodeCutout(topLeft: CGSize(width: imageTextInset + 6.0, height: 10.0))
textCutoutWidth = imageTextInset + 6.0
}
maxTitleNumberOfLines = 2
maxTextNumberOfLines = 5
if imageTextInset != 0.0 {
adjustedConstrainedTextSize.width += imageTextInset
textCutout = TextNodeCutout(topLeft: CGSize(width: imageTextInset + 6.0, height: 10.0))
textCutoutWidth = imageTextInset + 6.0
}
}
@ -723,10 +724,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
node.contentNode.view.insertSubview(node.backgroundView, at: 0)
}
var backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: realSize.width, height: realSize.height + 2.0))
if case .standalone = arguments.type {
backgroundFrame.size.height -= 1.0
}
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: realSize.width, height: realSize.height))
node.backgroundView.frame = backgroundFrame
@ -749,30 +747,6 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
animation: animation
)
let _ = secondaryColor
/*if let secondaryColor {
let lineDashView: UIImageView
if let current = node.lineDashView {
lineDashView = current
} else {
lineDashView = UIImageView(image: PresentationResourcesChat.chatReplyLineDashTemplateImage(arguments.presentationData.theme.theme, incoming: isIncoming))
lineDashView.clipsToBounds = true
node.lineDashView = lineDashView
node.contentNode.view.addSubview(lineDashView)
}
lineDashView.tintColor = secondaryColor
lineDashView.frame = CGRect(origin: .zero, size: CGSize(width: 12.0, height: backgroundFrame.height))
lineDashView.layer.cornerRadius = 6.0
if #available(iOS 13.0, *) {
lineDashView.layer.cornerCurve = .continuous
}
} else {
if let lineDashView = node.lineDashView {
node.lineDashView = nil
lineDashView.removeFromSuperview()
}
}*/
if arguments.quote != nil || arguments.replyForward?.quote != nil {
let quoteIconView: UIImageView
if let current = node.quoteIconView {

View File

@ -1068,8 +1068,10 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
}
}
var replyBackgroundFrame: CGRect?
if let (replyInfoSize, replyInfoApply) = replyInfoApply {
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: replyInfoSize)
replyBackgroundFrame = replyInfoFrame
let replyInfoNode = replyInfoApply(replyInfoFrame.size, synchronousLoads, animation)
if strongSelf.replyInfoNode == nil {
@ -1084,8 +1086,8 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
strongSelf.replyInfoNode = nil
}
if let replyBackgroundNode = strongSelf.replyBackgroundNode {
replyBackgroundNode.frame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)) - 4.0, y: headersOffset + 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0))
if let replyBackgroundNode = strongSelf.replyBackgroundNode, let replyBackgroundFrame {
replyBackgroundNode.frame = replyBackgroundFrame
let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0
replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: cornerRadius, transition: .immediate)
@ -1341,6 +1343,10 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
return .optionalAction({
item.controllerInteraction.navigateToStory(item.message, attribute.storyId)
})
} else if let attribute = attribute as? QuotedReplyMessageAttribute {
return .optionalAction({
item.controllerInteraction.attemptedNavigationToPrivateQuote(attribute.peerId.flatMap { item.message.peers[$0] })
})
}
}
}

View File

@ -1108,13 +1108,18 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
let enableCopy = !item.associatedData.isCopyProtectionEnabled && !item.message.isCopyProtected()
textSelectionNode.enableCopy = enableCopy
let enableQuote = true
var enableQuote = !item.message.text.isEmpty
var enableOtherActions = true
if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject, case .reply = info {
enableOtherActions = false
} else if item.controllerInteraction.canSetupReply(item.message) == .reply {
enableOtherActions = false
}
if !item.controllerInteraction.canSendMessages() && !enableCopy {
enableQuote = false
}
textSelectionNode.enableQuote = enableQuote
textSelectionNode.enableTranslate = enableOtherActions
textSelectionNode.enableShare = enableOtherActions

View File

@ -519,6 +519,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, setupReply: { _ in
}, canSetupReply: { _ in
return .none
}, canSendMessages: {
return false
}, navigateToFirstDateMessage: { _, _ in
}, requestRedeliveryOfFailedMessages: { _ in
}, addContact: { _ in
@ -571,6 +573,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, dismissTextInput: {
}, scrollToMessageId: { _ in
}, navigateToStory: { _, _ in
}, attemptedNavigationToPrivateQuote: { _ in
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: self.backgroundNode))
self.controllerInteraction = controllerInteraction

View File

@ -155,6 +155,7 @@ public final class ChatControllerInteraction {
public let openSearch: () -> Void
public let setupReply: (MessageId) -> Void
public let canSetupReply: (Message) -> ChatControllerInteractionSwipeAction
public let canSendMessages: () -> Bool
public let navigateToFirstDateMessage: (Int32, Bool) -> Void
public let requestRedeliveryOfFailedMessages: (MessageId) -> Void
public let addContact: (String) -> Void
@ -203,6 +204,7 @@ public final class ChatControllerInteraction {
public let dismissTextInput: () -> Void
public let scrollToMessageId: (MessageIndex) -> Void
public let navigateToStory: (Message, StoryId) -> Void
public let attemptedNavigationToPrivateQuote: (Peer?) -> Void
public var canPlayMedia: Bool = false
public var hiddenMedia: [MessageId: [Media]] = [:]
@ -270,6 +272,7 @@ public final class ChatControllerInteraction {
openSearch: @escaping () -> Void,
setupReply: @escaping (MessageId) -> Void,
canSetupReply: @escaping (Message) -> ChatControllerInteractionSwipeAction,
canSendMessages: @escaping () -> Bool,
navigateToFirstDateMessage: @escaping(Int32, Bool) ->Void,
requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void,
addContact: @escaping (String) -> Void,
@ -317,6 +320,7 @@ public final class ChatControllerInteraction {
dismissTextInput: @escaping () -> Void,
scrollToMessageId: @escaping (MessageIndex) -> Void,
navigateToStory: @escaping (Message, StoryId) -> Void,
attemptedNavigationToPrivateQuote: @escaping (Peer?) -> Void,
automaticMediaDownloadSettings: MediaAutoDownloadSettings,
pollActionState: ChatInterfacePollActionState,
stickerSettings: ChatInterfaceStickerSettings,
@ -367,6 +371,7 @@ public final class ChatControllerInteraction {
self.openSearch = openSearch
self.setupReply = setupReply
self.canSetupReply = canSetupReply
self.canSendMessages = canSendMessages
self.navigateToFirstDateMessage = navigateToFirstDateMessage
self.requestRedeliveryOfFailedMessages = requestRedeliveryOfFailedMessages
self.addContact = addContact
@ -414,6 +419,7 @@ public final class ChatControllerInteraction {
self.dismissTextInput = dismissTextInput
self.scrollToMessageId = scrollToMessageId
self.navigateToStory = navigateToStory
self.attemptedNavigationToPrivateQuote = attemptedNavigationToPrivateQuote
self.automaticMediaDownloadSettings = automaticMediaDownloadSettings

View File

@ -15,6 +15,7 @@ import ChatMessageTextBubbleContentNode
import TextFormat
import ChatMessageItemView
import ChatMessageBubbleItemNode
import TelegramNotices
private enum OptionsId: Hashable {
case reply
@ -286,12 +287,16 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch
return .complete()
}
let items = selfController.context.account.postbox.messagesAtIds([replySubject.messageId])
let items = combineLatest(queue: .mainQueue(),
selfController.context.account.postbox.messagesAtIds([replySubject.messageId]),
ApplicationSpecificNotice.getReplyQuoteTextSelectionTips(accountManager: selfController.context.sharedContext.accountManager)
)
|> deliverOnMainQueue
|> map { [weak selfController, weak chatController] messages -> [ContextMenuItem] in
|> map { [weak selfController, weak chatController] messages, quoteTextSelectionTips -> ContextController.Items in
guard let selfController, let chatController else {
return []
return ContextController.Items(content: .list([]))
}
var items: [ContextMenuItem] = []
if replySubject.quote != nil {
@ -398,18 +403,21 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch
})))
}
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Reply in Another Chat", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replace"), color: theme.contextMenu.primaryColor) }, action: { [weak selfController] c, f in
f(.default)
guard let selfController else {
return
}
guard let replySubject = selfController.presentationInterfaceState.interfaceState.replyMessageSubject else {
return
}
moveReplyMessageToAnotherChat(selfController: selfController, replySubject: replySubject)
})))
if selfController.presentationInterfaceState.copyProtectionEnabled || messages.first?.isCopyProtected() == true {
} else {
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Reply in Another Chat", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replace"), color: theme.contextMenu.primaryColor) }, action: { [weak selfController] c, f in
f(.default)
guard let selfController else {
return
}
guard let replySubject = selfController.presentationInterfaceState.interfaceState.replyMessageSubject else {
return
}
moveReplyMessageToAnotherChat(selfController: selfController, replySubject: replySubject)
})))
}
items.append(.separator)
@ -441,14 +449,17 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch
})))
}
return items
var tip: ContextController.Tip?
if quoteTextSelectionTips <= 3, let message = messages.first, !message.text.isEmpty {
tip = .quoteSelection
}
return ContextController.Items(content: .list(items), tip: tip)
}
var tip: ContextController.Tip?
if "".isEmpty {
tip = .quoteSelection
}
return items |> map { ContextController.Items(content: .list($0), tip: tip) }
let _ = ApplicationSpecificNotice.incrementReplyQuoteTextSelectionTips(accountManager: selfController.context.sharedContext.accountManager).startStandalone()
return items
}
private func chatReplyOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode, getContextController: @escaping () -> ContextController?, selectionState: Promise<ChatControllerSubject.MessageOptionsInfo.SelectionState>) -> ContextController.Source? {

View File

@ -0,0 +1,91 @@
import Foundation
import UIKit
import AsyncDisplayKit
import ContextUI
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramNotices
import ChatSendMessageActionUI
import AccountContext
func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, node: ASDisplayNode, gesture: ContextGesture) {
guard let peerId = selfController.chatLocation.peerId, let textInputView = selfController.chatDisplayNode.textInputView(), let layout = selfController.validLayout else {
return
}
let previousSupportedOrientations = selfController.supportedOrientations
if layout.size.width > layout.size.height {
selfController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .landscape)
} else {
selfController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
}
let _ = ApplicationSpecificNotice.incrementChatMessageOptionsTip(accountManager: selfController.context.sharedContext.accountManager, count: 4).startStandalone()
var hasEntityKeyboard = false
if case .media = selfController.presentationInterfaceState.inputMode {
hasEntityKeyboard = true
}
let _ = (selfController.context.account.viewTracker.peerView(peerId)
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { [weak selfController] peerView in
guard let selfController, let peer = peerViewMainPeer(peerView) else {
return
}
var sendWhenOnlineAvailable = false
if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence, case let .present(until) = presence.status {
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if currentTime > until {
sendWhenOnlineAvailable = true
}
}
if peer.id.namespace == Namespaces.Peer.CloudUser && peer.id.id._internalGetInt64Value() == 777000 {
sendWhenOnlineAvailable = false
}
if sendWhenOnlineAvailable {
let _ = ApplicationSpecificNotice.incrementSendWhenOnlineTip(accountManager: selfController.context.sharedContext.accountManager, count: 4).startStandalone()
}
let controller = ChatSendMessageActionSheetController(context: selfController.context, updatedPresentationData: selfController.updatedPresentationData, peerId: selfController.presentationInterfaceState.chatLocation.peerId, forwardMessageIds: selfController.presentationInterfaceState.interfaceState.forwardMessageIds, hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, sourceSendButton: node, textInputView: textInputView, canSendWhenOnline: sendWhenOnlineAvailable, completion: { [weak selfController] in
guard let selfController else {
return
}
selfController.supportedOrientations = previousSupportedOrientations
}, sendMessage: { [weak selfController] mode in
guard let selfController else {
return
}
switch mode {
case .generic:
selfController.controllerInteraction?.sendCurrentMessage(false)
case .silently:
selfController.controllerInteraction?.sendCurrentMessage(true)
case .whenOnline:
selfController.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleWhenOnlineTimestamp) { [weak selfController] in
guard let selfController else {
return
}
selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, saveInterfaceState: selfController.presentationInterfaceState.subject != .scheduledMessages, {
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) }
})
selfController.openScheduledMessages()
}
}
}, schedule: { [weak selfController] in
guard let selfController else {
return
}
selfController.controllerInteraction?.scheduleCurrentMessage()
})
controller.emojiViewProvider = selfController.chatDisplayNode.textInputPanelNode?.emojiViewProvider
selfController.sendMessageActionsController = controller
if layout.isNonExclusive {
selfController.present(controller, in: .window(.root))
} else {
selfController.presentInGlobalOverlay(controller, with: nil)
}
})
}

View File

@ -3448,7 +3448,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
}, openSearch: {
}, setupReply: { [weak self] messageId in
self?.interfaceInteraction?.setupReplyMessage(messageId, { _, _ in })
self?.interfaceInteraction?.setupReplyMessage(messageId, { _, f in f() })
}, canSetupReply: { [weak self] message in
if !message.flags.contains(.Incoming) {
if !message.flags.intersection([.Failed, .Sending, .Unsent]).isEmpty {
@ -3471,6 +3471,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
return .none
}, canSendMessages: { [weak self] in
guard let self else {
return false
}
return canSendMessagesToChat(self.presentationInterfaceState)
}, navigateToFirstDateMessage: { [weak self] timestamp, alreadyThere in
guard let strongSelf = self else {
return
@ -5011,6 +5016,24 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
)
self.push(storyContainerScreen)
})
}, attemptedNavigationToPrivateQuote: { [weak self] peer in
guard let self else {
return
}
//TODO:localize
let text: String
if let peer = peer as? TelegramChannel {
if case .broadcast = peer.info {
text = "This quote is from a private channel"
} else {
text = "This quote is from a private group"
}
} else if peer is TelegramGroup {
text = "This quote is from a private group"
} else {
text = "This quote is from a private chat"
}
self.controllerInteraction?.displayUndo(.info(title: nil, text: text, timeout: nil))
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: self.stickerSettings, presentationContext: ChatPresentationContext(context: context, backgroundNode: self.chatBackgroundNode))
controllerInteraction.enableFullTranslucency = context.sharedContext.energyUsageSettings.fullTranslucency
@ -10547,78 +10570,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.window?.presentInGlobalOverlay(slowmodeTooltipController)
}, displaySendMessageOptions: { [weak self] node, gesture in
if let strongSelf = self, let peerId = strongSelf.chatLocation.peerId, let textInputView = strongSelf.chatDisplayNode.textInputView(), let layout = strongSelf.validLayout {
let previousSupportedOrientations = strongSelf.supportedOrientations
if layout.size.width > layout.size.height {
strongSelf.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .landscape)
} else {
strongSelf.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
}
let _ = ApplicationSpecificNotice.incrementChatMessageOptionsTip(accountManager: strongSelf.context.sharedContext.accountManager, count: 4).startStandalone()
var hasEntityKeyboard = false
if case .media = strongSelf.presentationInterfaceState.inputMode {
hasEntityKeyboard = true
}
let _ = (strongSelf.context.account.viewTracker.peerView(peerId)
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { [weak self] peerView in
guard let strongSelf = self, let peer = peerViewMainPeer(peerView) else {
return
}
var sendWhenOnlineAvailable = false
if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence, case let .present(until) = presence.status {
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if currentTime > until {
sendWhenOnlineAvailable = true
}
}
if peer.id.namespace == Namespaces.Peer.CloudUser && peer.id.id._internalGetInt64Value() == 777000 {
sendWhenOnlineAvailable = false
}
if sendWhenOnlineAvailable {
let _ = ApplicationSpecificNotice.incrementSendWhenOnlineTip(accountManager: strongSelf.context.sharedContext.accountManager, count: 4).startStandalone()
}
let controller = ChatSendMessageActionSheetController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds, hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, sourceSendButton: node, textInputView: textInputView, canSendWhenOnline: sendWhenOnlineAvailable, completion: { [weak self] in
if let strongSelf = self {
strongSelf.supportedOrientations = previousSupportedOrientations
}
}, sendMessage: { [weak self] mode in
if let strongSelf = self {
switch mode {
case .generic:
strongSelf.controllerInteraction?.sendCurrentMessage(false)
case .silently:
strongSelf.controllerInteraction?.sendCurrentMessage(true)
case .whenOnline:
strongSelf.chatDisplayNode.sendCurrentMessage(scheduleTime: scheduleWhenOnlineTimestamp) { [weak self] in
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, saveInterfaceState: strongSelf.presentationInterfaceState.subject != .scheduledMessages, {
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) }
})
strongSelf.openScheduledMessages()
}
}
}
}
}, schedule: { [weak self] in
if let strongSelf = self {
strongSelf.controllerInteraction?.scheduleCurrentMessage()
}
})
controller.emojiViewProvider = strongSelf.chatDisplayNode.textInputPanelNode?.emojiViewProvider
strongSelf.sendMessageActionsController = controller
if layout.isNonExclusive {
strongSelf.present(controller, in: .window(.root))
} else {
strongSelf.presentInGlobalOverlay(controller, with: nil)
}
})
guard let self else {
return
}
chatMessageDisplaySendMessageOptions(selfController: self, node: node, gesture: gesture)
}, openScheduledMessages: { [weak self] in
if let strongSelf = self {
strongSelf.openScheduledMessages()

View File

@ -649,6 +649,10 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
}
if !canSendMessagesToChat(chatPresentationInterfaceState) && (chatPresentationInterfaceState.copyProtectionEnabled || message.isCopyProtected()) {
canReply = false
}
for media in messages[0].media {
if let story = media as? TelegramMediaStory {
if let story = message.associatedStories[story.storyId], story.data.isEmpty {

View File

@ -123,6 +123,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, setupReply: { _ in
}, canSetupReply: { _ in
return .none
}, canSendMessages: {
return false
}, navigateToFirstDateMessage: { _, _ in
}, requestRedeliveryOfFailedMessages: { _ in
}, addContact: { _ in
@ -172,6 +174,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, dismissTextInput: {
}, scrollToMessageId: { _ in
}, navigateToStory: { _, _ in
}, attemptedNavigationToPrivateQuote: { _ in
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
self.dimNode = ASDisplayNode()

View File

@ -2874,6 +2874,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}, setupReply: { _ in
}, canSetupReply: { _ in
return .none
}, canSendMessages: {
return false
}, navigateToFirstDateMessage: { _, _ in
}, requestRedeliveryOfFailedMessages: { _ in
}, addContact: { _ in
@ -2923,6 +2925,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}, dismissTextInput: {
}, scrollToMessageId: { _ in
}, navigateToStory: { _, _ in
}, attemptedNavigationToPrivateQuote: { _ in
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().startStrict(next: { [weak self] ids in

View File

@ -1512,6 +1512,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in
}, canSetupReply: { _ in
return .none
}, canSendMessages: {
return false
}, navigateToFirstDateMessage: { _, _ in
}, requestRedeliveryOfFailedMessages: { _ in
}, addContact: { _ in
@ -1561,6 +1563,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, dismissTextInput: {
}, scrollToMessageId: { _ in
}, navigateToStory: { _, _ in
}, attemptedNavigationToPrivateQuote: { _ in
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: backgroundNode as? WallpaperBackgroundNode))