diff --git a/Telegram/NotificationService/Sources/NotificationService.swift b/Telegram/NotificationService/Sources/NotificationService.swift index 3fa826778b..d3b040895c 100644 --- a/Telegram/NotificationService/Sources/NotificationService.swift +++ b/Telegram/NotificationService/Sources/NotificationService.swift @@ -988,6 +988,9 @@ private final class NotificationServiceHandler { if let threadId = aps["thread-id"] as? String { content.threadId = threadId } + if let threadIdValue = aps["topic_id"] as? String, let threadId = Int(threadIdValue) { + content.userInfo["threadId"] = Int32(clamping: threadId) + } if let ringtoneString = aps["ringtone"] as? String, let fileId = Int64(ringtoneString) { content.sound = "0.m4a" diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift index 0d3e40dd10..a0181137f6 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift @@ -234,13 +234,22 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat let readCounters = EnginePeerReadCounters(state: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, .idBased(maxIncomingReadId: 1, maxOutgoingReadId: 1, maxKnownId: 1, count: data.incomingUnreadCount, markedUnread: false))]), isMuted: false) + var draft: EngineChatList.Draft? + if let embeddedState = item.embeddedInterfaceState, let _ = embeddedState.overrideChatTimestamp { + if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) { + if let text = opaqueState.synchronizeableInputState?.text { + draft = EngineChatList.Draft(text: text, entities: opaqueState.synchronizeableInputState?.entities ?? []) + } + } + } + items.append(EngineChatList.Item( id: .forum(item.id), index: .forum(pinnedIndex: pinnedIndex, timestamp: item.index.timestamp, threadId: item.id, namespace: item.index.id.namespace, id: item.index.id.id), messages: item.topMessage.flatMap { [EngineMessage($0)] } ?? [], readCounters: readCounters, isMuted: isMuted, - draft: nil, + draft: draft, threadData: data, renderedPeer: EngineRenderedPeer(peer: EnginePeer(peer)), presence: nil, diff --git a/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift b/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift index a92e58394f..b685eaa561 100644 --- a/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift +++ b/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift @@ -8,6 +8,7 @@ final class MutableMessageHistoryThreadIndexView: MutablePostboxView { var info: CodableEntry var tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo] var topMessage: Message? + var embeddedInterfaceState: StoredPeerChatInterfaceState? init( id: Int64, @@ -15,7 +16,8 @@ final class MutableMessageHistoryThreadIndexView: MutablePostboxView { index: MessageIndex, info: CodableEntry, tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], - topMessage: Message? + topMessage: Message?, + embeddedInterfaceState: StoredPeerChatInterfaceState? ) { self.id = id self.pinnedIndex = pinnedIndex @@ -23,6 +25,7 @@ final class MutableMessageHistoryThreadIndexView: MutablePostboxView { self.info = info self.tagSummaryInfo = tagSummaryInfo self.topMessage = topMessage + self.embeddedInterfaceState = embeddedInterfaceState } } @@ -81,13 +84,17 @@ final class MutableMessageHistoryThreadIndexView: MutablePostboxView { ) } + var embeddedInterfaceState: StoredPeerChatInterfaceState? + embeddedInterfaceState = postbox.peerChatThreadInterfaceStateTable.get(PeerChatThreadId(peerId: self.peerId, threadId: item.threadId)) + self.items.append(Item( id: item.threadId, pinnedIndex: pinnedIndex, index: item.index, info: item.info.data, tagSummaryInfo: tagSummaryInfo, - topMessage: postbox.getMessage(item.index.id) + topMessage: postbox.getMessage(item.index.id), + embeddedInterfaceState: embeddedInterfaceState )) } @@ -110,7 +117,7 @@ final class MutableMessageHistoryThreadIndexView: MutablePostboxView { func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool { var updated = false - if transaction.updatedMessageThreadPeerIds.contains(self.peerId) || transaction.updatedPinnedThreads.contains(self.peerId) || transaction.updatedPeerThreadCombinedStates.contains(self.peerId) || transaction.currentUpdatedMessageTagSummaries.contains(where: { $0.key.peerId == self.peerId }) || transaction.currentUpdatedMessageActionsSummaries.contains(where: { $0.key.peerId == self.peerId }) { + if transaction.updatedMessageThreadPeerIds.contains(self.peerId) || transaction.updatedPinnedThreads.contains(self.peerId) || transaction.updatedPeerThreadCombinedStates.contains(self.peerId) || transaction.currentUpdatedMessageTagSummaries.contains(where: { $0.key.peerId == self.peerId }) || transaction.currentUpdatedMessageActionsSummaries.contains(where: { $0.key.peerId == self.peerId }) || transaction.currentUpdatedPeerChatListEmbeddedStates.contains(self.peerId) { self.reload(postbox: postbox) updated = true } @@ -135,6 +142,7 @@ public final class EngineMessageHistoryThread { public let info: CodableEntry public let tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo] public let topMessage: Message? + public let embeddedInterfaceState: StoredPeerChatInterfaceState? public init( id: Int64, @@ -142,7 +150,8 @@ public final class EngineMessageHistoryThread { index: MessageIndex, info: CodableEntry, tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], - topMessage: Message? + topMessage: Message?, + embeddedInterfaceState: StoredPeerChatInterfaceState? ) { self.id = id self.pinnedIndex = pinnedIndex @@ -150,6 +159,7 @@ public final class EngineMessageHistoryThread { self.info = info self.tagSummaryInfo = tagSummaryInfo self.topMessage = topMessage + self.embeddedInterfaceState = embeddedInterfaceState } public static func ==(lhs: Item, rhs: Item) -> Bool { @@ -178,6 +188,9 @@ public final class EngineMessageHistoryThread { } else if (lhs.topMessage == nil) != (rhs.topMessage == nil) { return false } + if lhs.embeddedInterfaceState != rhs.embeddedInterfaceState { + return false + } return true } @@ -200,7 +213,8 @@ public final class MessageHistoryThreadIndexView: PostboxView { index: item.index, info: item.info, tagSummaryInfo: item.tagSummaryInfo, - topMessage: item.topMessage + topMessage: item.topMessage, + embeddedInterfaceState: item.embeddedInterfaceState )) } self.items = items diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index bf7551df7b..30ade43b96 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -2329,6 +2329,7 @@ final class PostboxImpl { fileprivate func setPeerChatThreadInterfaceState(_ id: PeerId, threadId: Int64, state: StoredPeerChatInterfaceState?) { let updatedState = state let _ = self.peerChatThreadInterfaceStateTable.set(PeerChatThreadId(peerId: id, threadId: threadId), state: updatedState) + self.currentUpdatedPeerChatListEmbeddedStates.insert(id) } fileprivate func replaceRemoteContactCount(_ count: Int32) { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift index b701d67b4f..5f5f2c4020 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift @@ -109,13 +109,13 @@ func applySecretOutgoingMessageReadActions(transaction: Transaction, id: Message } } -func _internal_togglePeerUnreadMarkInteractively(postbox: Postbox, viewTracker: AccountViewTracker, peerId: PeerId, setToValue: Bool? = nil) -> Signal { +func _internal_togglePeerUnreadMarkInteractively(postbox: Postbox, network: Network, viewTracker: AccountViewTracker, peerId: PeerId, setToValue: Bool? = nil) -> Signal { return postbox.transaction { transaction -> Void in - _internal_togglePeerUnreadMarkInteractively(transaction: transaction, viewTracker: viewTracker, peerId: peerId, setToValue: setToValue) + _internal_togglePeerUnreadMarkInteractively(transaction: transaction, network: network, viewTracker: viewTracker, peerId: peerId, setToValue: setToValue) } } -func _internal_togglePeerUnreadMarkInteractively(transaction: Transaction, viewTracker: AccountViewTracker, peerId: PeerId, setToValue: Bool? = nil) { +func _internal_togglePeerUnreadMarkInteractively(transaction: Transaction, network: Network, viewTracker: AccountViewTracker, peerId: PeerId, setToValue: Bool? = nil) { guard let peer = transaction.getPeer(peerId) else { return } @@ -136,6 +136,11 @@ func _internal_togglePeerUnreadMarkInteractively(transaction: Transaction, viewT if let entry = StoredMessageHistoryThreadInfo(data) { transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: item.threadId, info: entry) } + + if let inputPeer = apiInputPeer(channel) { + //TODO:loc + let _ = network.request(Api.functions.messages.readDiscussion(peer: inputPeer, msgId: Int32(clamping: item.threadId), readMaxId: messageIndex.id.id)).start() + } } } } else { @@ -200,8 +205,8 @@ public func clearPeerUnseenReactionsInteractively(account: Account, peerId: Peer |> ignoreValues } -func _internal_markAllChatsAsReadInteractively(transaction: Transaction, viewTracker: AccountViewTracker, groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?) { +func _internal_markAllChatsAsReadInteractively(transaction: Transaction, network: Network, viewTracker: AccountViewTracker, groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?) { for peerId in transaction.getUnreadChatListPeerIds(groupId: groupId, filterPredicate: filterPredicate) { - _internal_togglePeerUnreadMarkInteractively(transaction: transaction, viewTracker: viewTracker, peerId: peerId, setToValue: false) + _internal_togglePeerUnreadMarkInteractively(transaction: transaction, network: network, viewTracker: viewTracker, peerId: peerId, setToValue: false) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 4f612b4678..e85ce4849a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -441,7 +441,7 @@ public extension TelegramEngine { let account = self.account return self.account.postbox.transaction { transaction -> Void in for (groupId, filterPredicate) in items { - _internal_markAllChatsAsReadInteractively(transaction: transaction, viewTracker: account.viewTracker, groupId: groupId._asGroup(), filterPredicate: filterPredicate) + _internal_markAllChatsAsReadInteractively(transaction: transaction, network: self.account.network, viewTracker: account.viewTracker, groupId: groupId._asGroup(), filterPredicate: filterPredicate) } } |> ignoreValues @@ -459,7 +459,7 @@ public extension TelegramEngine { public func togglePeersUnreadMarkInteractively(peerIds: [EnginePeer.Id], setToValue: Bool?) -> Signal { return self.account.postbox.transaction { transaction -> Void in for peerId in peerIds { - _internal_togglePeerUnreadMarkInteractively(transaction: transaction, viewTracker: self.account.viewTracker, peerId: peerId, setToValue: setToValue) + _internal_togglePeerUnreadMarkInteractively(transaction: transaction, network: self.account.network, viewTracker: self.account.viewTracker, peerId: peerId, setToValue: setToValue) } } |> ignoreValues diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index acbc310568..b1062bb4f0 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -782,7 +782,7 @@ private func extractAccountManagerState(records: AccountRecordsView map(Optional.init), voipNotificationToken: self.voipTokenPromise.get() |> map(Optional.init), setNotificationCall: { call in setPresentationCall?(call) }, navigateToChat: { accountId, peerId, messageId in - self.openChatWhenReady(accountId: accountId, peerId: peerId, messageId: messageId) + self.openChatWhenReady(accountId: accountId, peerId: peerId, threadId: nil, messageId: messageId) }, displayUpgradeProgress: { progress in if let progress = progress { if self.dataImportSplash == nil { @@ -1819,7 +1819,7 @@ private func extractAccountManagerState(records: AccountRecordsView take(1) |> deliverOnMainQueue @@ -1971,7 +1971,7 @@ private func extractAccountManagerState(records: AccountRecordsView deliverOnMainQueue).start(next: { context in - context.openChatWithPeerId(peerId: peerId, messageId: messageId, activateInput: activateInput) + context.openChatWithPeerId(peerId: peerId, threadId: threadId, messageId: messageId, activateInput: activateInput) })) } @@ -1995,15 +1995,15 @@ private func extractAccountManagerState(records: AccountRecordsView deliverOnMainQueue).start(next: { accountId in if response.actionIdentifier == UNNotificationDefaultActionIdentifier { - if let peerId = peerIdFromNotification(response.notification) { + if let (peerId, threadId) = peerIdFromNotification(response.notification) { var messageId: MessageId? = nil if response.notification.request.content.categoryIdentifier == "watch" { messageId = messageIdFromNotification(peerId: peerId, notification: response.notification) } - self.openChatWhenReady(accountId: accountId, peerId: peerId, messageId: messageId) + self.openChatWhenReady(accountId: accountId, peerId: peerId, threadId: threadId, messageId: messageId) } completionHandler() - } else if response.actionIdentifier == "reply", let peerId = peerIdFromNotification(response.notification), let accountId = accountId { + } else if response.actionIdentifier == "reply", let (peerId, _) = peerIdFromNotification(response.notification), let accountId = accountId { guard let response = response as? UNTextInputNotificationResponse, !response.userText.isEmpty else { completionHandler() return @@ -2385,11 +2385,13 @@ private func accountIdFromNotification(_ notification: UNNotification, sharedCon } @available(iOS 10.0, *) -private func peerIdFromNotification(_ notification: UNNotification) -> PeerId? { +private func peerIdFromNotification(_ notification: UNNotification) -> (peerId: PeerId, threadId: Int64?)? { + let threadId = notification.request.content.userInfo["threadId"] as? Int64 + if let peerId = notification.request.content.userInfo["peerId"] as? Int64 { - return PeerId(peerId) + return (PeerId(peerId), threadId) } else if let peerIdString = notification.request.content.userInfo["peerId"] as? String, let peerId = Int64(peerIdString) { - return PeerId(peerId) + return (PeerId(peerId), threadId) } else { let payload = notification.request.content.userInfo var peerId: PeerId? @@ -2406,7 +2408,12 @@ private func peerIdFromNotification(_ notification: UNNotification) -> PeerId? { let fromIdValue = fromId as! NSString peerId = PeerId(namespace: Namespaces.Peer.SecretChat, id: PeerId.Id._internalFromInt64Value(Int64(fromIdValue as String) ?? 0)) } - return peerId + + if let peerId = peerId { + return (peerId, threadId) + } else { + return nil + } } } diff --git a/submodules/TelegramUI/Sources/ApplicationContext.swift b/submodules/TelegramUI/Sources/ApplicationContext.swift index 1dff08d740..2e5c8cd99e 100644 --- a/submodules/TelegramUI/Sources/ApplicationContext.swift +++ b/submodules/TelegramUI/Sources/ApplicationContext.swift @@ -852,15 +852,24 @@ final class AuthorizedApplicationContext { })) } - func openChatWithPeerId(peerId: PeerId, messageId: MessageId? = nil, activateInput: Bool = false) { + func openChatWithPeerId(peerId: PeerId, threadId: Int64?, messageId: MessageId? = nil, activateInput: Bool = false) { var visiblePeerId: PeerId? - if let controller = self.rootController.topViewController as? ChatControllerImpl, case let .peer(peerId) = controller.chatLocation { + if let controller = self.rootController.topViewController as? ChatControllerImpl, controller.chatLocation.peerId == peerId, controller.chatLocation.threadId == threadId { visiblePeerId = peerId } if visiblePeerId != peerId || messageId != nil { if self.rootController.rootTabController != nil { - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: self.rootController, context: self.context, chatLocation: .peer(id: peerId), subject: messageId.flatMap { .message(id: .id($0), highlight: true, timecode: nil) }, activateInput: activateInput ? .text : nil)) + let chatLocation: ChatLocation + if let threadId = threadId { + chatLocation = .replyThread(message: ChatReplyThreadMessage( + messageId: MessageId(peerId: peerId, 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 { + chatLocation = .peer(id: peerId) + } + + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: self.rootController, context: self.context, chatLocation: chatLocation, subject: messageId.flatMap { .message(id: .id($0), highlight: true, timecode: nil) }, activateInput: activateInput ? .text : nil)) } else { self.scheduledOpenChatWithPeerId = (peerId, messageId, activateInput) } diff --git a/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift b/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift index 137f13ca94..965f09ee83 100644 --- a/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift @@ -133,10 +133,17 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { if firstMessage.id.peerId.isReplies, let _ = firstMessage.sourceReference, let effectiveAuthor = firstMessage.forwardInfo?.author { title = EnginePeer(effectiveAuthor).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + "@" + peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) } else if author.id != peer.id { + let authorString: String if author.id == item.context.account.peerId { - title = presentationData.strings.DialogList_You + "@" + peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + authorString = presentationData.strings.DialogList_You } else { - title = EnginePeer(author).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + "@" + peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + authorString = EnginePeer(author).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + } + + if let threadData = item.threadData { + title = "\(authorString) → \(threadData.info.title)" + } else { + title = authorString + "@" + peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) } } else { title = peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)