mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
Thread quote highlight
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user