Thread quote highlight

This commit is contained in:
Ali
2023-11-10 16:39:12 +04:00
parent cc3ea54e74
commit 843f5a6e91
3 changed files with 836 additions and 758 deletions

View File

@@ -1214,398 +1214,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}, openPeerMention: { [weak self] name, progress in
self?.openPeerMention(name, progress: progress)
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame, anyRecognizer, location in
guard let strongSelf = self, strongSelf.isNodeLoaded else {
guard let self, self.isNodeLoaded else {
return
}
if strongSelf.presentationInterfaceState.interfaceState.selectionState != nil {
return
}
let presentationData = strongSelf.presentationData
strongSelf.dismissAllTooltips()
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
let gesture: ContextGesture? = anyRecognizer as? ContextGesture
if let messages = strongSelf.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(message.id) {
(strongSelf.view.window as? WindowHost)?.cancelInteractiveKeyboardGestures()
strongSelf.chatDisplayNode.cancelInteractiveKeyboardGestures()
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
}
}
guard let topMessage = messages.first else {
return
}
let _ = combineLatest(queue: .mainQueue(),
strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId)),
contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: strongSelf.presentationInterfaceState, context: strongSelf.context, messages: updatedMessages, controllerInteraction: strongSelf.controllerInteraction, selectAll: selectAll, interfaceInteraction: strongSelf.interfaceInteraction, messageNode: node as? ChatMessageItemView),
peerMessageAllowedReactions(context: strongSelf.context, message: topMessage),
peerMessageSelectedReactions(context: strongSelf.context, message: topMessage),
topMessageReactions(context: strongSelf.context, message: topMessage),
ApplicationSpecificNotice.getChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager)
).startStandalone(next: { peer, actions, allowedReactions, selectedReactions, topReactions, chatTextSelectionTips in
guard let strongSelf = self else {
return
}
/*var hasPremium = false
if case let .user(user) = peer, user.isPremium {
hasPremium = true
}*/
var actions = actions
switch actions.content {
case let .list(itemList):
if itemList.isEmpty {
return
}
case .custom, .twoLists:
break
}
var tip: ContextController.Tip?
if tip == nil {
let isAd = message.adAttribute != nil
var isAction = false
for media in message.media {
if media is TelegramMediaAction {
isAction = true
break
}
}
if strongSelf.presentationInterfaceState.copyProtectionEnabled && !isAction && !isAd {
if case .scheduledMessages = strongSelf.subject {
} else {
var isChannel = false
if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, case .broadcast = channel.info {
isChannel = true
}
tip = .messageCopyProtection(isChannel: isChannel)
}
} else {
let numberOfComponents = message.text.components(separatedBy: CharacterSet.whitespacesAndNewlines).count
let displayTextSelectionTip = numberOfComponents >= 3 && !message.text.isEmpty && chatTextSelectionTips < 3 && !isAd
if displayTextSelectionTip {
let _ = ApplicationSpecificNotice.incrementChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager).startStandalone()
tip = .textSelection
}
}
}
if actions.tip == nil {
actions.tip = tip
}
actions.context = strongSelf.context
actions.animationCache = strongSelf.controllerInteraction?.presentationContext.animationCache
if canAddMessageReactions(message: topMessage), let allowedReactions = allowedReactions, !topReactions.isEmpty {
actions.reactionItems = topReactions.map(ReactionContextItem.reaction)
actions.selectedReactionItems = selectedReactions.reactions
if !actions.reactionItems.isEmpty {
let reactionItems: [EmojiComponentReactionItem] = actions.reactionItems.compactMap { item -> EmojiComponentReactionItem? in
switch item {
case let .reaction(reaction):
return EmojiComponentReactionItem(reaction: reaction.reaction.rawValue, file: reaction.stillAnimation)
default:
return nil
}
}
var allReactionsAreAvailable = false
switch allowedReactions {
case .set:
allReactionsAreAvailable = false
case .all:
allReactionsAreAvailable = true
}
if let channel = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramChannel, case .broadcast = channel.info {
allReactionsAreAvailable = false
}
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
if premiumConfiguration.isPremiumDisabled {
allReactionsAreAvailable = false
}
if allReactionsAreAvailable {
actions.getEmojiContent = { animationCache, animationRenderer in
guard let strongSelf = self else {
preconditionFailure()
}
return EmojiPagerContentComponent.emojiInputData(
context: strongSelf.context,
animationCache: animationCache,
animationRenderer: animationRenderer,
isStandalone: false,
subject: .reaction,
hasTrending: false,
topReactionItems: reactionItems,
areUnicodeEmojiEnabled: false,
areCustomEmojiEnabled: true,
chatPeerId: strongSelf.chatLocation.peerId,
selectedItems: selectedReactions.files
)
}
}
}
}
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
let presentationContext = strongSelf.controllerInteraction?.presentationContext
var disableTransitionAnimations = false
var actionsSignal: Signal<ContextController.Items, NoError> = .single(actions)
if let entitiesAttribute = message.textEntitiesAttribute {
var emojiFileIds: [Int64] = []
for entity in entitiesAttribute.entities {
if case let .CustomEmoji(_, fileId) = entity.type {
emojiFileIds.append(fileId)
}
}
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
if !emojiFileIds.isEmpty && !premiumConfiguration.isPremiumDisabled {
tip = .animatedEmoji(text: nil, arguments: nil, file: nil, action: nil)
actions.tip = tip
disableTransitionAnimations = true
actionsSignal = .single(actions)
|> then(
context.engine.stickers.resolveInlineStickers(fileIds: emojiFileIds)
|> mapToSignal { files -> Signal<ContextController.Items, NoError> in
var packReferences: [StickerPackReference] = []
var existingIds = Set<Int64>()
for (_, file) in files {
loop: for attribute in file.attributes {
if case let .CustomEmoji(_, _, _, packReference) = attribute, let packReference = packReference {
if case let .id(id, _) = packReference, !existingIds.contains(id) {
packReferences.append(packReference)
existingIds.insert(id)
}
break loop
}
}
}
let action = {
guard let strongSelf = self else {
return
}
strongSelf.presentEmojiList(references: packReferences)
}
if packReferences.count > 1 {
actions.tip = .animatedEmoji(text: presentationData.strings.ChatContextMenu_EmojiSet(Int32(packReferences.count)), arguments: nil, file: nil, action: action)
return .single(actions)
} else if let reference = packReferences.first {
return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
|> filter { result in
if case .result = result {
return true
} else {
return false
}
}
|> mapToSignal { result in
if case let .result(info, items, _) = result, let presentationContext = presentationContext {
actions.tip = .animatedEmoji(
text: presentationData.strings.ChatContextMenu_EmojiSetSingle(info.title).string,
arguments: TextNodeWithEntities.Arguments(
context: context,
cache: presentationContext.animationCache,
renderer: presentationContext.animationRenderer,
placeholderColor: .clear,
attemptSynchronous: true
),
file: items.first?.file,
action: action)
return .single(actions)
} else {
return .complete()
}
}
} else {
actions.tip = nil
return .single(actions)
}
}
)
}
}
let source: ContextContentSource
if let location = location {
source = .location(ChatMessageContextLocationContentSource(controller: strongSelf, location: node.view.convert(node.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y)))
} else {
source = .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, engine: strongSelf.context.engine, message: message, selectAll: selectAll))
}
strongSelf.canReadHistory.set(false)
let controller = ContextController(presentationData: strongSelf.presentationData, source: source, items: actionsSignal, recognizer: recognizer, gesture: gesture)
controller.dismissed = { [weak self] in
self?.canReadHistory.set(true)
}
controller.immediateItemsTransitionAnimation = disableTransitionAnimations
controller.getOverlayViews = { [weak self] in
guard let strongSelf = self else {
return []
}
return [strongSelf.chatDisplayNode.navigateButtons.view]
}
strongSelf.currentContextController = controller
controller.premiumReactionsSelected = { [weak controller] in
guard let strongSelf = self else {
return
}
controller?.dismissWithoutContent()
let context = strongSelf.context
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumDemoScreen(context: context, subject: .uniqueReactions, action: {
let controller = PremiumIntroScreen(context: context, source: .reactions)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
strongSelf.push(controller)
}
controller.reactionSelected = { [weak controller] chosenUpdatedReaction, isLarge in
guard let strongSelf = self else {
return
}
guard let message = messages.first else {
return
}
controller?.view.endEditing(true)
let chosenReaction: MessageReaction.Reaction = chosenUpdatedReaction.reaction
let currentReactions = mergedMessageReactions(attributes: message.attributes)?.reactions ?? []
var updatedReactions: [MessageReaction.Reaction] = currentReactions.filter(\.isSelected).map(\.value)
var removedReaction: MessageReaction.Reaction?
var isFirst = false
if let index = updatedReactions.firstIndex(where: { $0 == chosenReaction }) {
removedReaction = chosenReaction
updatedReactions.remove(at: index)
} else {
updatedReactions.append(chosenReaction)
isFirst = !currentReactions.contains(where: { $0.value == chosenReaction })
}
/*guard let allowedReactions = allowedReactions else {
itemNode.openMessageContextMenu()
return
}
switch allowedReactions {
case let .set(set):
if !messageAlreadyHasThisReaction && updatedReactions.contains(where: { !set.contains($0) }) {
itemNode.openMessageContextMenu()
return
}
case .all:
break
}*/
if removedReaction == nil, case .custom = chosenReaction {
if !strongSelf.presentationInterfaceState.isPremium {
controller?.premiumReactionsSelected?()
return
}
}
strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item {
if item.message.id == message.id {
if removedReaction == nil && !updatedReactions.isEmpty {
itemNode.awaitingAppliedReaction = (chosenReaction, { [weak itemNode] in
guard let controller = controller else {
return
}
if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: chosenReaction) {
strongSelf.chatDisplayNode.messageTransitionNode.addMessageContextController(messageId: item.message.id, contextController: controller)
var hideTargetButton: UIView?
if isFirst {
hideTargetButton = targetView.superview
}
controller.dismissWithReaction(value: chosenReaction, targetView: targetView, hideNode: true, animateTargetContainer: hideTargetButton, addStandaloneReactionAnimation: { standaloneReactionAnimation in
guard let strongSelf = self else {
return
}
strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
}, completion: { [weak itemNode, weak targetView] in
guard let strongSelf = self, let itemNode = itemNode, let targetView = targetView else {
return
}
let _ = strongSelf
let _ = itemNode
let _ = targetView
})
} else {
controller.dismiss()
}
})
} else {
itemNode.awaitingAppliedReaction = (nil, {
controller?.dismiss()
})
}
}
}
}
let mappedUpdatedReactions = updatedReactions.map { reaction -> UpdateMessageReaction in
switch reaction {
case let .builtin(value):
return .builtin(value)
case let .custom(fileId):
var customFile: TelegramMediaFile?
if case let .custom(customFileId, file) = chosenUpdatedReaction, fileId == customFileId {
customFile = file
}
return .custom(fileId: fileId, file: customFile)
}
}
let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageId: message.id, reactions: mappedUpdatedReactions, isLarge: isLarge, storeAsRecentlyUsed: true).startStandalone()
}
strongSelf.forEachController({ controller in
if let controller = controller as? TooltipScreen {
controller.dismiss()
}
return true
})
strongSelf.window?.presentInGlobalOverlay(controller)
})
}
self.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame, anyRecognizer: anyRecognizer, location: location)
}, openMessageReactionContextMenu: { [weak self] message, sourceView, gesture, value in
guard let strongSelf = self else {
return
@@ -2026,46 +1638,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let self else {
return
}
let continueNavigation: () -> Void = { [weak self] in
guard let self else {
return
}
self.navigateToMessage(from: fromId, to: .id(id, params), forceInCurrentChat: fromId.peerId == id.peerId)
}
let _ = (self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: id.peerId)
)
|> deliverOnMainQueue).startStandalone(next: { [weak self] toPeer in
guard let self else {
return
}
if params.quote != nil {
if let toPeer {
switch toPeer {
case let .channel(channel):
if channel.username == nil && channel.usernames.isEmpty {
switch channel.participationStatus {
case .kicked, .left:
self.controllerInteraction?.attemptedNavigationToPrivateQuote(toPeer._asPeer())
return
case .member:
break
}
}
default:
break
}
} else {
self.controllerInteraction?.attemptedNavigationToPrivateQuote(nil)
return
}
}
continueNavigation()
})
self.navigateToMessage(fromId: fromId, id: id, params: params)
}, navigateToMessageStandalone: { [weak self] id in
self?.navigateToMessage(from: nil, to: .id(id, NavigateToMessageParams(timestamp: nil, quote: nil)), forceInCurrentChat: false)
}, navigateToThreadMessage: { [weak self] peerId, threadId, messageId in
@@ -8114,11 +7687,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.Conversation_MessageDoesntExist, timeout: nil, customUndoText: nil), elevatedLayout: false, action: { _ in return true }), in: .current)
}
} else if let controllerInteraction = strongSelf.controllerInteraction {
if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(index.id) {
var mappedId = index.id
if index.timestamp == 0 {
if case let .replyThread(message) = strongSelf.chatLocation, let channelMessageId = message.channelMessageId {
mappedId = channelMessageId
}
}
if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(mappedId) {
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: [])
strongSelf.scrolledToMessageIdValue = ScrolledToMessageId(id: mappedId, allowedReplacementDirection: [])
var hasQuote = false
if let quote = toSubject.quote {
@@ -16368,332 +15948,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.navigateToMessage(from: nil, to: messageLocation, scrollPosition: scrollPosition, rememberInStack: false, forceInCurrentChat: forceInCurrentChat, dropStack: dropStack, animated: animated, completion: completion, customPresentProgress: customPresentProgress)
}
func navigateToMessage(from fromId: MessageId?, to messageLocation: NavigateToMessageLocation, scrollPosition: ListViewScrollPosition = .center(.bottom), rememberInStack: Bool = true, forceInCurrentChat: Bool = false, dropStack: Bool = false, animated: Bool = true, completion: (() -> Void)? = nil, customPresentProgress: ((ViewController, Any?) -> Void)? = nil, statusSubject: ChatLoadingMessageSubject = .generic) {
if self.isNodeLoaded {
var fromIndex: MessageIndex?
if let fromId = fromId, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(fromId) {
fromIndex = message.index
} else {
if let message = self.chatDisplayNode.historyNode.anchorMessageInCurrentHistoryView() {
fromIndex = message.index
}
}
var isScheduledMessages = false
var isPinnedMessages = false
if case .scheduledMessages = self.presentationInterfaceState.subject {
isScheduledMessages = true
} else if case .pinnedMessages = self.presentationInterfaceState.subject {
isPinnedMessages = true
}
var forceInCurrentChat = forceInCurrentChat
if case let .peer(peerId) = self.chatLocation, messageLocation.peerId == peerId, !isPinnedMessages, !isScheduledMessages {
forceInCurrentChat = true
}
if isPinnedMessages, let messageId = messageLocation.messageId {
let _ = (combineLatest(
self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId)),
self.context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .local)
|> mapToSignal { result -> Signal<[Message], NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
)
|> deliverOnMainQueue).startStandalone(next: { [weak self] peer, messages in
guard let self, let peer = peer else {
return
}
guard let navigationController = self.effectiveNavigationController else {
return
}
self.dismiss()
let navigateToLocation: NavigateToChatControllerParams.Location
if let message = messages.first, let threadId = message.threadId, let channel = message.peers[message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum) {
navigateToLocation = .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))
} else {
navigateToLocation = .peer(peer)
}
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: navigateToLocation, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil), keepStack: .always))
})
} else if case let .peer(peerId) = self.chatLocation, let messageId = messageLocation.messageId, (messageId.peerId != peerId && !forceInCurrentChat) || (isScheduledMessages && messageId.id != 0 && !Namespaces.Message.allScheduled.contains(messageId.namespace)) {
let _ = (self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId),
TelegramEngine.EngineData.Item.Messages.Message(id: messageId)
)
|> deliverOnMainQueue).startStandalone(next: { [weak self] peer, message in
guard let self, let peer = peer else {
return
}
if let navigationController = self.effectiveNavigationController {
var chatLocation: NavigateToChatControllerParams.Location = .peer(peer)
if case let .channel(channel) = peer, channel.flags.contains(.isForum), let message = message, let threadId = message.threadId {
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: ChatControllerSubject.MessageHighlight.Quote?
if case let .id(_, params) = messageLocation {
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))
}
})
} else if forceInCurrentChat {
if let _ = fromId, let fromIndex = fromIndex, rememberInStack {
self.historyNavigationStack.add(fromIndex)
}
let scrollFromIndex: MessageIndex?
if let fromIndex = fromIndex {
scrollFromIndex = fromIndex
} else if let message = self.chatDisplayNode.historyNode.lastVisbleMesssage() {
scrollFromIndex = message.index
} else {
scrollFromIndex = nil
}
if let scrollFromIndex = scrollFromIndex {
if let messageId = messageLocation.messageId, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {
self.loadingMessage.set(.single(nil))
self.messageIndexDisposable.set(nil)
var delayCompletion = true
if self.chatDisplayNode.historyNode.isMessageVisible(id: messageId) {
delayCompletion = false
}
var quote: (string: String, offset: Int?)?
if case let .id(_, params) = messageLocation {
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)
if delayCompletion {
Queue.mainQueue().after(0.25, {
completion?()
})
} else {
Queue.mainQueue().justDispatch({
completion?()
})
}
if case let .id(_, params) = messageLocation, let timecode = params.timestamp {
let _ = self.controllerInteraction?.openMessage(message, OpenMessageParams(mode: .timecode(timecode)))
}
} else if case let .index(index) = messageLocation, index.id.id == 0, index.timestamp > 0, case .scheduledMessages = self.presentationInterfaceState.subject {
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: index, animated: animated, scrollPosition: scrollPosition)
} else {
if case let .id(messageId, params) = messageLocation, params.timestamp != nil {
self.scheduledScrollToMessageId = (messageId, params)
}
var progress: Promise<Bool>?
if case let .id(_, params) = messageLocation {
progress = params.progress
}
self.loadingMessage.set(.single(statusSubject) |> delay(0.1, queue: .mainQueue()))
let searchLocation: ChatHistoryInitialSearchLocation
switch messageLocation {
case let .id(id, _):
searchLocation = .id(id)
case let .index(index):
searchLocation = .index(index)
case .upperBound:
if let peerId = self.chatLocation.peerId {
searchLocation = .index(MessageIndex.upperBound(peerId: peerId))
} else {
searchLocation = .index(.absoluteUpperBound())
}
}
var historyView: Signal<ChatHistoryViewUpdate, NoError>
historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: searchLocation, quote: nil), 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?, Bool), NoError>
signal = historyView
|> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in
switch historyView {
case .Loading:
return .single((nil, true))
case let .HistoryView(view, _, _, _, _, _, _):
for entry in view.entries {
if entry.message.id == messageLocation.messageId {
return .single((entry.message.index, false))
}
}
if case let .index(index) = searchLocation {
return .single((index, false))
}
return .single((nil, false))
}
}
|> take(until: { index in
return SignalTakeAction(passthrough: true, complete: !index.1)
})
/*#if DEBUG
signal = .single((nil, true)) |> then(signal |> delay(2.0, queue: .mainQueue()))
#endif*/
var cancelImpl: (() -> Void)?
let presentationData = self.presentationData
let displayTime = CACurrentMediaTime()
let progressSignal = Signal<Never, NoError> { [weak self] subscriber in
if let progress {
progress.set(.single(true))
return ActionDisposable {
Queue.mainQueue().async() {
progress.set(.single(false))
}
}
} else if case .generic = statusSubject {
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
if CACurrentMediaTime() - displayTime > 1.5 {
cancelImpl?()
}
}))
if let customPresentProgress = customPresentProgress {
customPresentProgress(controller, nil)
} else {
self?.present(controller, in: .window(.root))
}
return ActionDisposable { [weak controller] in
Queue.mainQueue().async() {
controller?.dismiss()
}
}
} else {
return EmptyDisposable
}
}
|> runOn(Queue.mainQueue())
|> delay(0.05, queue: Queue.mainQueue())
let progressDisposable = MetaDisposable()
var progressStarted = false
self.messageIndexDisposable.set((signal
|> afterDisposed {
Queue.mainQueue().async {
progressDisposable.dispose()
}
}
|> deliverOnMainQueue).startStrict(next: { [weak self] index in
if let strongSelf = self, let index = index.0 {
strongSelf.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: index, animated: animated, scrollPosition: scrollPosition)
completion?()
} else if index.1 {
if !progressStarted {
progressStarted = true
progressDisposable.set(progressSignal.start())
}
}
}, completed: { [weak self] in
if let strongSelf = self {
strongSelf.loadingMessage.set(.single(nil))
}
}))
cancelImpl = { [weak self] in
if let strongSelf = self {
strongSelf.loadingMessage.set(.single(nil))
strongSelf.messageIndexDisposable.set(nil)
}
}
}
} else {
completion?()
}
} else {
if let fromIndex = fromIndex {
let searchLocation: ChatHistoryInitialSearchLocation
switch messageLocation {
case let .id(id, _):
searchLocation = .id(id)
case let .index(index):
searchLocation = .index(index)
case .upperBound:
return
}
if let _ = fromId, rememberInStack {
self.historyNavigationStack.add(fromIndex)
}
self.loadingMessage.set(.single(statusSubject) |> delay(0.1, queue: .mainQueue()))
var quote: ChatControllerSubject.MessageHighlight.Quote?
if case let .id(_, params) = messageLocation {
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.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
switch historyView {
case .Loading:
return .complete()
case let .HistoryView(view, _, _, _, _, _, _):
for entry in view.entries {
if entry.message.id == messageLocation.messageId {
return .single(entry.message.index)
}
}
return .single(nil)
}
}
|> take(1)
self.messageIndexDisposable.set((signal |> deliverOnMainQueue).startStrict(next: { [weak self] index in
if let strongSelf = self {
if let index = index {
strongSelf.chatDisplayNode.historyNode.scrollToMessage(from: fromIndex, to: index, animated: animated, scrollPosition: scrollPosition)
completion?()
} else {
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageLocation.peerId))
|> deliverOnMainQueue).startStandalone(next: { peer in
guard let strongSelf = self, let peer = peer else {
return
}
if let navigationController = strongSelf.effectiveNavigationController {
var quote: ChatControllerSubject.MessageHighlight.Quote?
if case let .id(_, params) = messageLocation {
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) }))
}
})
completion?()
}
}
}, completed: { [weak self] in
if let strongSelf = self {
strongSelf.loadingMessage.set(.single(nil))
}
}))
} else {
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageLocation.peerId))
|> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
guard let self, let peer = peer else {
return
}
if let navigationController = self.effectiveNavigationController {
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), subject: messageLocation.messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil) }))
}
completion?()
})
}
}
} else {
completion?()
}
}
func forwardMessages(messageIds: [MessageId], options: ChatInterfaceForwardOptionsState? = nil, resetCurrent: Bool = false) {
let _ = (self.context.engine.data.get(EngineDataMap(
messageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init)