mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 01:10:09 +00:00
Precise quote support
This commit is contained in:
parent
8051e43e4c
commit
2a02a41ac2
@ -578,10 +578,12 @@ public enum ChatControllerSubject: Equatable {
|
||||
public struct Quote: Equatable {
|
||||
public let messageId: EngineMessage.Id
|
||||
public let text: String
|
||||
public let offset: Int?
|
||||
|
||||
public init(messageId: EngineMessage.Id, text: String) {
|
||||
public init(messageId: EngineMessage.Id, text: String, offset: Int?) {
|
||||
self.messageId = messageId
|
||||
self.text = text
|
||||
self.offset = offset
|
||||
}
|
||||
}
|
||||
|
||||
@ -645,9 +647,19 @@ public enum ChatControllerSubject: Equatable {
|
||||
}
|
||||
|
||||
public struct MessageHighlight: Equatable {
|
||||
public var quote: String?
|
||||
public struct Quote: Equatable {
|
||||
public var string: String
|
||||
public var offset: Int?
|
||||
|
||||
public init(quote: String? = nil) {
|
||||
public init(string: String, offset: Int?) {
|
||||
self.string = string
|
||||
self.offset = offset
|
||||
}
|
||||
}
|
||||
|
||||
public var quote: Quote?
|
||||
|
||||
public init(quote: Quote? = nil) {
|
||||
self.quote = quote
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,20 +8,40 @@ public enum ChatHistoryInitialSearchLocation: Equatable {
|
||||
}
|
||||
|
||||
public struct MessageHistoryScrollToSubject: Equatable {
|
||||
public var index: MessageHistoryAnchorIndex
|
||||
public var quote: String?
|
||||
public struct Quote: Equatable {
|
||||
public var string: String
|
||||
public var offset: Int?
|
||||
|
||||
public init(index: MessageHistoryAnchorIndex, quote: String?) {
|
||||
public init(string: String, offset: Int?) {
|
||||
self.string = string
|
||||
self.offset = offset
|
||||
}
|
||||
}
|
||||
|
||||
public var index: MessageHistoryAnchorIndex
|
||||
public var quote: Quote?
|
||||
|
||||
public init(index: MessageHistoryAnchorIndex, quote: Quote?) {
|
||||
self.index = index
|
||||
self.quote = quote
|
||||
}
|
||||
}
|
||||
|
||||
public struct MessageHistoryInitialSearchSubject: Equatable {
|
||||
public var location: ChatHistoryInitialSearchLocation
|
||||
public var quote: String?
|
||||
public struct Quote: Equatable {
|
||||
public var string: String
|
||||
public var offset: Int?
|
||||
|
||||
public init(location: ChatHistoryInitialSearchLocation, quote: String?) {
|
||||
public init(string: String, offset: Int?) {
|
||||
self.string = string
|
||||
self.offset = offset
|
||||
}
|
||||
}
|
||||
|
||||
public var location: ChatHistoryInitialSearchLocation
|
||||
public var quote: Quote?
|
||||
|
||||
public init(location: ChatHistoryInitialSearchLocation, quote: Quote?) {
|
||||
self.location = location
|
||||
self.quote = quote
|
||||
}
|
||||
|
||||
@ -2043,7 +2043,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
return .optionalAction({
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote?.text : nil))
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote.flatMap { quote in NavigateToMessageParams.Quote(string: quote.text, offset: quote.offset) } : nil))
|
||||
})
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
return .optionalAction({
|
||||
|
||||
@ -563,7 +563,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
private var backgroundType: ChatMessageBackgroundType?
|
||||
|
||||
private struct HighlightedState: Equatable {
|
||||
var quote: String?
|
||||
var quote: ChatInterfaceHighlightedState.Quote?
|
||||
}
|
||||
private var highlightedState: HighlightedState?
|
||||
|
||||
@ -4055,7 +4055,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
if let replyInfoNode = self.replyInfoNode {
|
||||
progress = replyInfoNode.makeProgress()
|
||||
}
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote?.text : nil, progress: progress))
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote.flatMap { quote in NavigateToMessageParams.Quote(string: quote.text, offset: quote.offset) } : nil, progress: progress))
|
||||
}, contextMenuOnLongPress: true))
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
return .action(InternalBubbleTapAction.Action({
|
||||
@ -4692,7 +4692,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
|
||||
for contentNode in self.contentNodes {
|
||||
if let contentNode = contentNode as? ChatMessageTextBubbleContentNode {
|
||||
contentNode.updateQuoteTextHighlightState(text: nil, color: .clear, animated: true)
|
||||
contentNode.updateQuoteTextHighlightState(text: nil, offset: nil, color: .clear, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -4745,7 +4745,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
var quoteFrame: CGRect?
|
||||
for contentNode in self.contentNodes {
|
||||
if let contentNode = contentNode as? ChatMessageTextBubbleContentNode {
|
||||
contentNode.updateQuoteTextHighlightState(text: quote, color: highlightColor, animated: false)
|
||||
contentNode.updateQuoteTextHighlightState(text: quote.string, offset: quote.offset, 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
|
||||
@ -5186,10 +5186,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
return nil
|
||||
}
|
||||
|
||||
public func getQuoteRect(quote: String) -> CGRect? {
|
||||
public func getQuoteRect(quote: String, offset: Int?) -> CGRect? {
|
||||
for contentNode in self.contentNodes {
|
||||
if let contentNode = contentNode as? ChatMessageTextBubbleContentNode {
|
||||
if let result = contentNode.getQuoteRect(quote: quote) {
|
||||
if let result = contentNode.getQuoteRect(quote: quote, offset: offset) {
|
||||
return contentNode.view.convert(result, to: self.view)
|
||||
}
|
||||
}
|
||||
|
||||
@ -963,7 +963,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
return .optionalAction({
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote?.text : nil))
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote.flatMap { quote in NavigateToMessageParams.Quote(string: quote.text, offset: quote.offset) } : nil))
|
||||
})
|
||||
} else if let attribute = attribute as? QuotedReplyMessageAttribute {
|
||||
return .action(InternalBubbleTapAction.Action {
|
||||
|
||||
@ -1345,7 +1345,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
if let item = self.item {
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote?.text : nil))
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote.flatMap { quote in NavigateToMessageParams.Quote(string: quote.text, offset: quote.offset) } : nil))
|
||||
return
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
item.controllerInteraction.navigateToStory(item.message, attribute.storyId)
|
||||
|
||||
@ -1371,7 +1371,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
return .optionalAction({
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote?.text : nil))
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote.flatMap { quote in NavigateToMessageParams.Quote(string: quote.text, offset: quote.offset) } : nil))
|
||||
})
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
return .optionalAction({
|
||||
|
||||
@ -53,6 +53,34 @@ private final class CachedChatMessageText {
|
||||
}
|
||||
}
|
||||
|
||||
private func findQuoteRange(string: String, quoteText: String, offset: Int?) -> NSRange? {
|
||||
let nsString = string as NSString
|
||||
var currentRange: NSRange?
|
||||
while true {
|
||||
let startOffset = currentRange?.upperBound ?? 0
|
||||
let range = nsString.range(of: quoteText, range: NSRange(location: startOffset, length: nsString.length - startOffset))
|
||||
if range.location != NSNotFound {
|
||||
if let offset {
|
||||
if let currentRangeValue = currentRange {
|
||||
if abs(range.location - offset) > abs(currentRangeValue.location - offset) {
|
||||
break
|
||||
} else {
|
||||
currentRange = range
|
||||
}
|
||||
} else {
|
||||
currentRange = range
|
||||
}
|
||||
} else {
|
||||
currentRange = range
|
||||
break
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return currentRange
|
||||
}
|
||||
|
||||
public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
private let containerNode: ASDisplayNode
|
||||
private let textNode: TextNodeWithEntities
|
||||
@ -1109,17 +1137,15 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
public func getQuoteRect(quote: String) -> CGRect? {
|
||||
public func getQuoteRect(quote: String, offset: Int?) -> CGRect? {
|
||||
var rectsSet: [CGRect] = []
|
||||
if !quote.isEmpty, let cachedLayout = self.textNode.textNode.cachedLayout, let string = cachedLayout.attributedString?.string {
|
||||
let nsString = string as NSString
|
||||
let range = nsString.range(of: quote)
|
||||
if range.location != NSNotFound {
|
||||
if let rects = cachedLayout.rangeRects(in: range)?.rects, !rects.isEmpty {
|
||||
|
||||
let range = findQuoteRange(string: string, quoteText: quote, offset: offset)
|
||||
if let range, let rects = cachedLayout.rangeRects(in: range)?.rects, !rects.isEmpty {
|
||||
rectsSet = rects
|
||||
}
|
||||
}
|
||||
}
|
||||
if !rectsSet.isEmpty {
|
||||
var currentRect = CGRect()
|
||||
for rect in rectsSet {
|
||||
@ -1136,17 +1162,15 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
public func updateQuoteTextHighlightState(text: String?, color: UIColor, animated: Bool) {
|
||||
public func updateQuoteTextHighlightState(text: String?, offset: Int?, 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 {
|
||||
|
||||
let quoteRange = findQuoteRange(string: string, quoteText: text, offset: offset)
|
||||
if let quoteRange, let rects = cachedLayout.rangeRects(in: quoteRange)?.rects, !rects.isEmpty {
|
||||
rectsSet = rects
|
||||
}
|
||||
}
|
||||
}
|
||||
if !rectsSet.isEmpty {
|
||||
let rects = rectsSet
|
||||
let textHighlightNode: LinkHighlightingNode
|
||||
@ -1339,12 +1363,12 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
private func getSelectionState(range: NSRange?) -> ChatControllerSubject.MessageOptionsInfo.SelectionState {
|
||||
var quote: ChatControllerSubject.MessageOptionsInfo.Quote?
|
||||
if let item = self.item, let range, let selection = self.getCurrentTextSelection(customRange: range) {
|
||||
quote = ChatControllerSubject.MessageOptionsInfo.Quote(messageId: item.message.id, text: selection.text)
|
||||
quote = ChatControllerSubject.MessageOptionsInfo.Quote(messageId: item.message.id, text: selection.text, offset: selection.offset)
|
||||
}
|
||||
return ChatControllerSubject.MessageOptionsInfo.SelectionState(canQuote: true, quote: quote)
|
||||
}
|
||||
|
||||
public func getCurrentTextSelection(customRange: NSRange? = nil) -> (text: String, entities: [MessageTextEntity])? {
|
||||
public func getCurrentTextSelection(customRange: NSRange? = nil) -> (text: String, entities: [MessageTextEntity], offset: Int)? {
|
||||
guard let textSelectionNode = self.textSelectionNode else {
|
||||
return nil
|
||||
}
|
||||
@ -1359,13 +1383,14 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
let nsString = string.string as NSString
|
||||
let substring = nsString.substring(with: range)
|
||||
let offset = range.location
|
||||
|
||||
var entities: [MessageTextEntity] = []
|
||||
if let textEntitiesAttribute = item.message.textEntitiesAttribute {
|
||||
entities = messageTextEntitiesInRange(entities: textEntitiesAttribute.entities, range: range, onlyQuoteable: true)
|
||||
}
|
||||
|
||||
return (substring, entities)
|
||||
return (substring, entities, offset)
|
||||
}
|
||||
|
||||
public func animateClippingTransition(offset: CGFloat, animation: ListViewItemUpdateAnimation) {
|
||||
|
||||
@ -19,10 +19,20 @@ import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
|
||||
public struct ChatInterfaceHighlightedState: Equatable {
|
||||
public let messageStableId: UInt32
|
||||
public let quote: String?
|
||||
public struct Quote: Equatable {
|
||||
public var string: String
|
||||
public var offset: Int?
|
||||
|
||||
public init(messageStableId: UInt32, quote: String?) {
|
||||
public init(string: String, offset: Int?) {
|
||||
self.string = string
|
||||
self.offset = offset
|
||||
}
|
||||
}
|
||||
|
||||
public let messageStableId: UInt32
|
||||
public let quote: Quote?
|
||||
|
||||
public init(messageStableId: UInt32, quote: Quote?) {
|
||||
self.messageStableId = messageStableId
|
||||
self.quote = quote
|
||||
}
|
||||
@ -74,11 +84,21 @@ public protocol ChatMessageTransitionProtocol: ASDisplayNode {
|
||||
}
|
||||
|
||||
public struct NavigateToMessageParams {
|
||||
public struct Quote {
|
||||
public var string: String
|
||||
public var offset: Int?
|
||||
|
||||
public init(string: String, offset: Int?) {
|
||||
self.string = string
|
||||
self.offset = offset
|
||||
}
|
||||
}
|
||||
|
||||
public var timestamp: Double?
|
||||
public var quote: String?
|
||||
public var quote: Quote?
|
||||
public var progress: Promise<Bool>?
|
||||
|
||||
public init(timestamp: Double?, quote: String?, progress: Promise<Bool>? = nil) {
|
||||
public init(timestamp: Double?, quote: Quote?, progress: Promise<Bool>? = nil) {
|
||||
self.timestamp = timestamp
|
||||
self.quote = quote
|
||||
self.progress = progress
|
||||
|
||||
@ -302,7 +302,7 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch
|
||||
var quote: EngineMessageReplyQuote?
|
||||
let trimmedText = trimStringWithEntities(string: textSelection.text, entities: textSelection.entities, maxLength: quoteMaxLength(appConfig: selfController.context.currentAppConfiguration.with({ $0 })))
|
||||
if !trimmedText.string.isEmpty {
|
||||
quote = EngineMessageReplyQuote(text: trimmedText.string, offset: nil, entities: trimmedText.entities, media: nil)
|
||||
quote = EngineMessageReplyQuote(text: trimmedText.string, offset: textSelection.offset, entities: trimmedText.entities, media: nil)
|
||||
}
|
||||
|
||||
selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(ChatInterfaceState.ReplyMessageSubject(messageId: replySubject.messageId, quote: quote)).withoutSelectionState() }) })
|
||||
@ -367,7 +367,7 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch
|
||||
var quote: EngineMessageReplyQuote?
|
||||
let trimmedText = trimStringWithEntities(string: textSelection.text, entities: textSelection.entities, maxLength: quoteMaxLength(appConfig: selfController.context.currentAppConfiguration.with({ $0 })))
|
||||
if !trimmedText.string.isEmpty {
|
||||
quote = EngineMessageReplyQuote(text: trimmedText.string, offset: nil, entities: trimmedText.entities, media: nil)
|
||||
quote = EngineMessageReplyQuote(text: trimmedText.string, offset: textSelection.offset, entities: trimmedText.entities, media: nil)
|
||||
}
|
||||
|
||||
selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(ChatInterfaceState.ReplyMessageSubject(messageId: replySubject.messageId, quote: quote)).withoutSelectionState() }) })
|
||||
@ -492,7 +492,7 @@ private func chatReplyOptions(selfController: ChatControllerImpl, sourceNode: AS
|
||||
|
||||
var replyQuote: ChatControllerSubject.MessageOptionsInfo.Quote?
|
||||
if let quote = replySubject.quote {
|
||||
replyQuote = ChatControllerSubject.MessageOptionsInfo.Quote(messageId: replySubject.messageId, text: quote.text)
|
||||
replyQuote = ChatControllerSubject.MessageOptionsInfo.Quote(messageId: replySubject.messageId, text: quote.text, offset: quote.offset)
|
||||
}
|
||||
selectionState.set(selfController.context.account.postbox.messagesAtIds([replySubject.messageId])
|
||||
|> map { messages -> ChatControllerSubject.MessageOptionsInfo.SelectionState in
|
||||
|
||||
@ -794,7 +794,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
case .pinnedMessageUpdated:
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote?.text : nil)))
|
||||
strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote.flatMap { quote in NavigateToMessageParams.Quote(string: quote.text, offset: quote.offset) } : nil)))
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -803,7 +803,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
case .gameScore:
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote?.text : nil)))
|
||||
strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote.flatMap { quote in NavigateToMessageParams.Quote(string: quote.text, offset: quote.offset) } : nil)))
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -3893,7 +3893,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
let trimmedText = trimStringWithEntities(string: quoteText, entities: messageTextEntitiesInRange(entities: message.textEntitiesAttribute?.entities ?? [], range: nsRange, onlyQuoteable: true), maxLength: quoteMaxLength(appConfig: strongSelf.context.currentAppConfiguration.with({ $0 })))
|
||||
if !trimmedText.string.isEmpty {
|
||||
quoteData = EngineMessageReplyQuote(text: trimmedText.string, offset: nil, entities: trimmedText.entities, media: nil)
|
||||
quoteData = EngineMessageReplyQuote(text: trimmedText.string, offset: nsRange.location, entities: trimmedText.entities, media: nil)
|
||||
}
|
||||
|
||||
let replySubject = ChatInterfaceState.ReplyMessageSubject(
|
||||
@ -8070,14 +8070,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
} else if let controllerInteraction = strongSelf.controllerInteraction {
|
||||
if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(index.id) {
|
||||
let highlightedState = ChatInterfaceHighlightedState(messageStableId: message.stableId, quote: toSubject.quote)
|
||||
let highlightedState = ChatInterfaceHighlightedState(messageStableId: message.stableId, quote: toSubject.quote.flatMap { quote in ChatInterfaceHighlightedState.Quote(string: quote.string, offset: quote.offset) })
|
||||
controllerInteraction.highlightedState = highlightedState
|
||||
strongSelf.updateItemNodesHighlightedStates(animated: initial)
|
||||
strongSelf.scrolledToMessageIdValue = ScrolledToMessageId(id: index.id, allowedReplacementDirection: [])
|
||||
|
||||
var hasQuote = false
|
||||
if let quote = toSubject.quote {
|
||||
if message.text.contains(quote) {
|
||||
if message.text.contains(quote.string) {
|
||||
hasQuote = true
|
||||
} else {
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.Chat_ToastQuoteNotFound, timeout: nil, customUndoText: nil), elevatedLayout: false, action: { _ in return true }), in: .current)
|
||||
@ -16392,9 +16392,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
chatLocation = .replyThread(ChatReplyThreadMessage(messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false))
|
||||
}
|
||||
|
||||
var quote: String?
|
||||
var quote: ChatControllerSubject.MessageHighlight.Quote?
|
||||
if case let .id(_, params) = messageLocation {
|
||||
quote = params.quote
|
||||
quote = params.quote.flatMap { quote in ChatControllerSubject.MessageHighlight.Quote(string: quote.string, offset: quote.offset) }
|
||||
}
|
||||
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: chatLocation, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: quote), timecode: nil), keepStack: .always))
|
||||
@ -16424,9 +16424,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
delayCompletion = false
|
||||
}
|
||||
|
||||
var quote: String?
|
||||
var quote: (string: String, offset: Int?)?
|
||||
if case let .id(_, params) = messageLocation {
|
||||
quote = params.quote
|
||||
quote = params.quote.flatMap { quote in (string: quote.string, offset: quote.offset) }
|
||||
}
|
||||
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: message.index, animated: animated, quote: quote, scrollPosition: scrollPosition)
|
||||
|
||||
@ -16579,12 +16579,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
self.loadingMessage.set(.single(statusSubject) |> delay(0.1, queue: .mainQueue()))
|
||||
|
||||
var quote: String?
|
||||
var quote: ChatControllerSubject.MessageHighlight.Quote?
|
||||
if case let .id(_, params) = messageLocation {
|
||||
quote = params.quote
|
||||
quote = params.quote.flatMap { quote in ChatControllerSubject.MessageHighlight.Quote(string: quote.string, offset: quote.offset) }
|
||||
}
|
||||
|
||||
let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: searchLocation, quote: quote), count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||
let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: searchLocation, quote: quote.flatMap { quote in MessageHistoryInitialSearchSubject.Quote(string: quote.string, offset: quote.offset) }), count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||
var signal: Signal<MessageIndex?, NoError>
|
||||
signal = historyView
|
||||
|> mapToSignal { historyView -> Signal<MessageIndex?, NoError> in
|
||||
@ -16615,9 +16615,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
if let navigationController = strongSelf.effectiveNavigationController {
|
||||
var quote: String?
|
||||
var quote: ChatControllerSubject.MessageHighlight.Quote?
|
||||
if case let .id(_, params) = messageLocation {
|
||||
quote = params.quote
|
||||
quote = params.quote.flatMap { quote in ChatControllerSubject.MessageHighlight.Quote(string: quote.string, offset: quote.offset) }
|
||||
}
|
||||
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), subject: messageLocation.messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: quote), timecode: nil) }))
|
||||
|
||||
@ -504,7 +504,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
return (messages, Int32(messages.count), false)
|
||||
}
|
||||
source = .custom(messages: messages, messageId: messageIds.first ?? MessageId(peerId: PeerId(0), namespace: 0, id: 0), quote: reply.quote?.text, loadMore: nil)
|
||||
source = .custom(messages: messages, messageId: messageIds.first ?? MessageId(peerId: PeerId(0), namespace: 0, id: 0), quote: reply.quote.flatMap { quote in ChatHistoryListSource.Quote(text: quote.text, offset: quote.offset) }, loadMore: nil)
|
||||
case let .link(link):
|
||||
let messages = link.options
|
||||
|> mapToSignal { options -> Signal<(ChatControllerSubject.LinkOptions, Peer, Message?, [StoryId: CodableEntry]), NoError> in
|
||||
|
||||
@ -417,8 +417,18 @@ private struct ChatHistoryAnimatedEmojiConfiguration {
|
||||
private var nextClientId: Int32 = 1
|
||||
|
||||
public enum ChatHistoryListSource {
|
||||
public struct Quote {
|
||||
public var text: String
|
||||
public var offset: Int?
|
||||
|
||||
public init(text: String, offset: Int?) {
|
||||
self.text = text
|
||||
self.offset = offset
|
||||
}
|
||||
}
|
||||
|
||||
case `default`
|
||||
case custom(messages: Signal<([Message], Int32, Bool), NoError>, messageId: MessageId, quote: String?, loadMore: (() -> Void)?)
|
||||
case custom(messages: Signal<([Message], Int32, Bool), NoError>, messageId: MessageId, quote: Quote?, loadMore: (() -> Void)?)
|
||||
}
|
||||
|
||||
public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
@ -818,7 +828,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
initialSearchLocation = .index(MessageIndex.absoluteUpperBound())
|
||||
}
|
||||
}
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: initialSearchLocation, quote: highlight?.quote), count: historyMessageCount, highlight: highlight != nil), id: 0)
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: initialSearchLocation, quote: (highlight?.quote).flatMap { quote in MessageHistoryInitialSearchSubject.Quote(string: quote.string, offset: quote.offset) }), count: historyMessageCount, highlight: highlight != nil), id: 0)
|
||||
} else if let subject = subject, case let .pinnedMessages(maybeMessageId) = subject, let messageId = maybeMessageId {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(messageId), quote: nil), count: historyMessageCount, highlight: true), id: 0)
|
||||
} else {
|
||||
@ -1098,7 +1108,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
|
||||
let scrollPosition: ChatHistoryViewScrollPosition?
|
||||
if isFirstTime, let messageIndex = messages.first(where: { $0.id == at })?.index {
|
||||
scrollPosition = .index(subject: MessageHistoryScrollToSubject(index: .message(messageIndex), quote: quote), position: .center(.bottom), directionHint: .Down, animated: false, highlight: false, displayLink: false)
|
||||
scrollPosition = .index(subject: MessageHistoryScrollToSubject(index: .message(messageIndex), quote: quote.flatMap { quote in MessageHistoryScrollToSubject.Quote(string: quote.text, offset: quote.offset) }), position: .center(.bottom), directionHint: .Down, animated: false, highlight: false, displayLink: false)
|
||||
isFirstTime = false
|
||||
} else {
|
||||
scrollPosition = nil
|
||||
@ -1376,7 +1386,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
initialSearchLocation = .index(.absoluteUpperBound())
|
||||
}
|
||||
}
|
||||
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: initialSearchLocation, quote: highlight?.quote), count: historyMessageCount, highlight: highlight != nil), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
||||
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: initialSearchLocation, quote: (highlight?.quote).flatMap { quote in MessageHistoryInitialSearchSubject.Quote(string: quote.string, offset: quote.offset) }), count: historyMessageCount, highlight: highlight != nil), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
||||
} else if let subject = subject, case let .pinnedMessages(maybeMessageId) = subject, let messageId = maybeMessageId {
|
||||
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(messageId), quote: nil), count: historyMessageCount, highlight: true), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
||||
} else if var chatHistoryLocation = strongSelf.chatHistoryLocationValue {
|
||||
@ -2632,8 +2642,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
}
|
||||
}
|
||||
|
||||
public func scrollToMessage(from fromIndex: MessageIndex, to toIndex: MessageIndex, animated: Bool, highlight: Bool = true, quote: String? = nil, scrollPosition: ListViewScrollPosition = .center(.bottom)) {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(subject: MessageHistoryScrollToSubject(index: .message(toIndex), quote: quote), anchorIndex: .message(toIndex), sourceIndex: .message(fromIndex), scrollPosition: scrollPosition, animated: animated, highlight: highlight), id: self.takeNextHistoryLocationId())
|
||||
public func scrollToMessage(from fromIndex: MessageIndex, to toIndex: MessageIndex, animated: Bool, highlight: Bool = true, quote: (string: String, offset: Int?)? = nil, scrollPosition: ListViewScrollPosition = .center(.bottom)) {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(subject: MessageHistoryScrollToSubject(index: .message(toIndex), quote: quote.flatMap { quote in MessageHistoryScrollToSubject.Quote(string: quote.string, offset: quote.offset) }), anchorIndex: .message(toIndex), sourceIndex: .message(fromIndex), scrollPosition: scrollPosition, animated: animated, highlight: highlight), id: self.takeNextHistoryLocationId())
|
||||
}
|
||||
|
||||
public func anchorMessageInCurrentHistoryView() -> Message? {
|
||||
|
||||
@ -227,7 +227,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, ignoreMess
|
||||
|
||||
preloaded = true
|
||||
|
||||
return .HistoryView(view: view, type: reportUpdateType, scrollPosition: .index(subject: MessageHistoryScrollToSubject(index: anchorIndex, quote: searchLocationSubject.quote), position: .center(.bottom), directionHint: .Down, animated: false, highlight: highlight, displayLink: false), flashIndicators: false, originalScrollPosition: nil, initialData: ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData), id: location.id)
|
||||
return .HistoryView(view: view, type: reportUpdateType, scrollPosition: .index(subject: MessageHistoryScrollToSubject(index: anchorIndex, quote: searchLocationSubject.quote.flatMap { quote in MessageHistoryScrollToSubject.Quote(string: quote.string, offset: quote.offset) }), position: .center(.bottom), directionHint: .Down, animated: false, highlight: highlight, displayLink: false), flashIndicators: false, originalScrollPosition: nil, initialData: ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData), id: location.id)
|
||||
}
|
||||
}
|
||||
case let .Navigation(index, anchorIndex, count, _):
|
||||
|
||||
@ -96,7 +96,7 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
|
||||
if case let .id(messageId) = messageSubject {
|
||||
let navigationController = params.navigationController
|
||||
let animated = params.animated
|
||||
controller.navigateToMessage(messageLocation: .id(messageId, NavigateToMessageParams(timestamp: timecode, quote: highlight?.quote)), animated: isFirst, completion: { [weak navigationController, weak controller] in
|
||||
controller.navigateToMessage(messageLocation: .id(messageId, NavigateToMessageParams(timestamp: timecode, quote: (highlight?.quote).flatMap { quote in NavigateToMessageParams.Quote(string: quote.string, offset: quote.offset) })), animated: isFirst, completion: { [weak navigationController, weak controller] in
|
||||
if let navigationController = navigationController, let controller = controller {
|
||||
let _ = navigationController.popToViewController(controller, animated: animated)
|
||||
}
|
||||
|
||||
@ -203,7 +203,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie
|
||||
if case .center = position, let quote = scrollSubject.quote {
|
||||
position = .center(.custom({ itemNode in
|
||||
if let itemNode = itemNode as? ChatMessageBubbleItemNode {
|
||||
if let quoteRect = itemNode.getQuoteRect(quote: quote) {
|
||||
if let quoteRect = itemNode.getQuoteRect(quote: quote.string, offset: quote.offset) {
|
||||
return quoteRect.midY
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user