diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 757e2ed170..7aae802834 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -1297,7 +1297,15 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { if let data = view.cachedData as? CachedUserData { return data.sendPaidMessageStars } else if let channel = peerViewMainPeer(view) as? TelegramChannel { - return channel.sendPaidMessageStars + if channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId { + if let mainChannel = view.peers[linkedMonoforumId] as? TelegramChannel { + return mainChannel.sendPaidMessageStars + } else { + return nil + } + } else { + return channel.sendPaidMessageStars + } } else { return nil } diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index 5cae045448..581b4b0238 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -4023,7 +4023,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel } headerNode.updateFlashingOnScrolling(flashing, animated: false) headerNode.frame = headerFrame - headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset, transition: animateInsertion ? .immediate : transition.0) + headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset, transition: .immediate) headerNode.updateInternalStickLocationDistanceFactor(stickLocationDistanceFactor, animated: false) self.itemHeaderNodes[id] = headerNode if insertItemBelowOtherHeaders { diff --git a/submodules/Postbox/Sources/Message.swift b/submodules/Postbox/Sources/Message.swift index ec71c30358..d875bfee09 100644 --- a/submodules/Postbox/Sources/Message.swift +++ b/submodules/Postbox/Sources/Message.swift @@ -1070,6 +1070,16 @@ public struct PeerAndThreadId: Hashable { } } +public struct PeerAndBoundThreadId: Hashable { + public var peerId: PeerId + public var threadId: Int64 + + public init(peerId: PeerId, threadId: Int64) { + self.peerId = peerId + self.threadId = threadId + } +} + public struct MessageAndThreadId: Hashable { public var messageId: MessageId public var threadId: Int64? diff --git a/submodules/Postbox/Sources/MessageHistorySavedMessagesIndexView.swift b/submodules/Postbox/Sources/MessageHistorySavedMessagesIndexView.swift index 61a46b3577..e41b1974bc 100644 --- a/submodules/Postbox/Sources/MessageHistorySavedMessagesIndexView.swift +++ b/submodules/Postbox/Sources/MessageHistorySavedMessagesIndexView.swift @@ -7,19 +7,22 @@ final class MutableMessageHistorySavedMessagesIndexView: MutablePostboxView { let pinnedIndex: Int? let index: MessageIndex let topMessage: Message? + let unreadCount: Int init( id: Int64, peer: Peer?, pinnedIndex: Int?, index: MessageIndex, - topMessage: Message? + topMessage: Message?, + unreadCount: Int ) { self.id = id self.peer = peer self.pinnedIndex = pinnedIndex self.index = index self.topMessage = topMessage + self.unreadCount = unreadCount } } @@ -67,7 +70,8 @@ final class MutableMessageHistorySavedMessagesIndexView: MutablePostboxView { peer: postbox.peerTable.get(PeerId(item.threadId)), pinnedIndex: pinnedIndex, index: item.index, - topMessage: postbox.getMessage(item.index.id) + topMessage: postbox.getMessage(item.index.id), + unreadCount: Int(item.info.summary.totalUnreadCount) )) } @@ -120,19 +124,22 @@ public final class EngineMessageHistorySavedMessagesThread { public let pinnedIndex: Int? public let index: MessageIndex public let topMessage: Message? + public let unreadCount: Int public init( id: Int64, peer: Peer?, pinnedIndex: Int?, index: MessageIndex, - topMessage: Message? + topMessage: Message?, + unreadCount: Int ) { self.id = id self.peer = peer self.pinnedIndex = pinnedIndex self.index = index self.topMessage = topMessage + self.unreadCount = unreadCount } public static func ==(lhs: Item, rhs: Item) -> Bool { @@ -158,6 +165,9 @@ public final class EngineMessageHistorySavedMessagesThread { } else if (lhs.topMessage == nil) != (rhs.topMessage == nil) { return false } + if lhs.unreadCount != rhs.unreadCount { + return false + } return true } @@ -179,7 +189,8 @@ public final class MessageHistorySavedMessagesIndexView: PostboxView { peer: item.peer, pinnedIndex: item.pinnedIndex, index: item.index, - topMessage: item.topMessage + topMessage: item.topMessage, + unreadCount: item.unreadCount )) } self.items = items diff --git a/submodules/Postbox/Sources/PeerView.swift b/submodules/Postbox/Sources/PeerView.swift index a8149a6ad9..3f907ca142 100644 --- a/submodules/Postbox/Sources/PeerView.swift +++ b/submodules/Postbox/Sources/PeerView.swift @@ -48,10 +48,15 @@ final class MutablePeerView: MutablePostboxView { var messageIds = Set() peerIds.insert(peerId) - if let peer = getPeer(peerId), let associatedPeerId = peer.associatedPeerId, peer.associatedPeerOverridesIdentity { + if let peer = getPeer(peerId), let associatedPeerId = peer.associatedPeerId { peerIds.insert(associatedPeerId) - self.contactPeerId = associatedPeerId - self.peerIsContact = postbox.contactsTable.isContact(peerId: associatedPeerId) + + if peer.associatedPeerOverridesIdentity { + self.contactPeerId = associatedPeerId + self.peerIsContact = postbox.contactsTable.isContact(peerId: associatedPeerId) + } else { + self.contactPeerId = peerId + } } else { self.contactPeerId = peerId } @@ -76,14 +81,18 @@ final class MutablePeerView: MutablePostboxView { self.memberStoryStats[id] = value } } - if let peer = self.peers[peerId], let associatedPeerId = peer.associatedPeerId, peer.associatedPeerOverridesIdentity { + if let peer = self.peers[peerId], let associatedPeerId = peer.associatedPeerId { if let peer = getPeer(associatedPeerId) { self.peers[associatedPeerId] = peer } if let presence = getPeerPresence(associatedPeerId) { self.peerPresences[associatedPeerId] = presence } - self.notificationSettings = postbox.peerNotificationSettingsTable.getEffective(associatedPeerId) + if peer.associatedPeerOverridesIdentity { + self.notificationSettings = postbox.peerNotificationSettingsTable.getEffective(associatedPeerId) + } else { + self.notificationSettings = postbox.peerNotificationSettingsTable.getEffective(peerId) + } } else { self.notificationSettings = postbox.peerNotificationSettingsTable.getEffective(peerId) } diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index be1a0091b5..d1d020024d 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -1249,7 +1249,7 @@ public final class ShareController: ViewController { private func shareModern(text: String, peerIds: [EnginePeer.Id], topicIds: [EnginePeer.Id: Int64], showNames: Bool, silently: Bool) -> Signal { return self.currentContext.stateManager.postbox.combinedView( keys: peerIds.map { peerId in - return PostboxViewKey.basicPeer(peerId) + return PostboxViewKey.peer(peerId: peerId, components: []) } + peerIds.map { peerId in return PostboxViewKey.cachedPeerData(peerId: peerId) } @@ -1259,14 +1259,20 @@ public final class ShareController: ViewController { var result: [EnginePeer.Id: EnginePeer?] = [:] var requiresStars: [EnginePeer.Id: StarsAmount] = [:] for peerId in peerIds { - if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView, let peer = view.peer { + if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? PeerView, let peer = peerViewMainPeer(view) { result[peerId] = EnginePeer(peer) if peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView { if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData { requiresStars[peerId] = cachedData.sendPaidMessageStars } } else if let channel = peer as? TelegramChannel { - requiresStars[peerId] = channel.sendPaidMessageStars + if channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId { + if let mainChannel = view.peers[linkedMonoforumId] as? TelegramChannel { + requiresStars[peerId] = mainChannel.sendPaidMessageStars + } + } else { + requiresStars[peerId] = channel.sendPaidMessageStars + } } } } @@ -1903,7 +1909,7 @@ public final class ShareController: ViewController { } return currentContext.stateManager.postbox.combinedView( keys: peerIds.map { peerId in - return PostboxViewKey.basicPeer(peerId) + return PostboxViewKey.peer(peerId: peerId, components: []) } + peerIds.map { peerId in return PostboxViewKey.cachedPeerData(peerId: peerId) } @@ -1913,14 +1919,20 @@ public final class ShareController: ViewController { var result: [EnginePeer.Id: EnginePeer?] = [:] var requiresStars: [EnginePeer.Id: StarsAmount] = [:] for peerId in peerIds { - if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView, let peer = view.peer { + if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? PeerView, let peer = peerViewMainPeer(view) { result[peerId] = EnginePeer(peer) if peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView { if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData { requiresStars[peerId] = cachedData.sendPaidMessageStars } } else if let channel = peer as? TelegramChannel { - requiresStars[peerId] = channel.sendPaidMessageStars + if channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId { + if let mainChannel = view.peers[linkedMonoforumId] as? TelegramChannel { + requiresStars[peerId] = mainChannel.sendPaidMessageStars + } + } else { + requiresStars[peerId] = channel.sendPaidMessageStars + } } } } @@ -2518,6 +2530,9 @@ public final class ShareController: ViewController { possiblePremiumRequiredPeers.insert(user.id) } else if let channel = peer as? TelegramChannel, let _ = channel.sendPaidMessageStars { possiblePremiumRequiredPeers.insert(channel.id) + if channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = entryData.renderedPeer.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.sendPaidMessageStars != nil { + possiblePremiumRequiredPeers.insert(channel.id) + } } } default: @@ -2531,7 +2546,7 @@ public final class ShareController: ViewController { keys.append(peerPresencesKey) for id in possiblePremiumRequiredPeers { - keys.append(.basicPeer(id)) + keys.append(.peer(peerId: id, components: [])) keys.append(.cachedPeerData(peerId: id)) } @@ -2549,8 +2564,12 @@ public final class ShareController: ViewController { if let view = views.views[.cachedPeerData(peerId: id)] as? CachedPeerDataView, let data = view.cachedPeerData as? CachedUserData { requiresPremiumForMessaging[id] = data.flags.contains(.premiumRequired) requiresStars[id] = data.sendPaidMessageStars?.value - } else if let view = views.views[.basicPeer(id)] as? BasicPeerView, let channel = view.peer as? TelegramChannel { - requiresStars[id] = channel.sendPaidMessageStars?.value + } else if let view = views.views[.basicPeer(id)] as? PeerView, let channel = peerViewMainPeer(view) as? TelegramChannel { + if channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = view.peers[linkedMonoforumId] as? TelegramChannel { + requiresStars[id] = mainChannel.sendPaidMessageStars?.value + } else { + requiresStars[id] = channel.sendPaidMessageStars?.value + } } else { requiresPremiumForMessaging[id] = false } diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 462c3a0737..6c453f4459 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -1274,7 +1274,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate if let context = self.context, let tryShare = self.tryShare { let _ = (context.stateManager.postbox.combinedView( keys: peerIds.map { peerId in - return PostboxViewKey.basicPeer(peerId) + return PostboxViewKey.peer(peerId: peerId, components: []) } + peerIds.map { peerId in return PostboxViewKey.cachedPeerData(peerId: peerId) } @@ -1284,14 +1284,18 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate var result: [EnginePeer.Id: EnginePeer?] = [:] var requiresStars: [EnginePeer.Id: Int64] = [:] for peerId in peerIds { - if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView, let peer = view.peer { + if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? PeerView, let peer = peerViewMainPeer(view) { result[peerId] = EnginePeer(peer) if peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView { if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData { requiresStars[peerId] = cachedData.sendPaidMessageStars?.value } } else if let channel = peer as? TelegramChannel { - requiresStars[peerId] = channel.sendPaidMessageStars?.value + if channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = view.peers[linkedMonoforumId] as? TelegramChannel { + requiresStars[peerId] = mainChannel.sendPaidMessageStars?.value + } else { + requiresStars[peerId] = channel.sendPaidMessageStars?.value + } } } } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index e6d624d75c..e622379cc1 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -872,6 +872,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1054465340] = { return Api.RichText.parse_textUnderline($0) } dict[1009288385] = { return Api.RichText.parse_textUrl($0) } dict[289586518] = { return Api.SavedContact.parse_savedPhoneContact($0) } + dict[2099641667] = { return Api.SavedDialog.parse_monoForumDialog($0) } dict[-1115174036] = { return Api.SavedDialog.parse_savedDialog($0) } dict[-881854424] = { return Api.SavedReactionTag.parse_savedReactionTag($0) } dict[-539360103] = { return Api.SavedStarGift.parse_savedStarGift($0) } @@ -1064,8 +1065,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1512627963] = { return Api.Update.parse_updateDialogFilterOrder($0) } dict[889491791] = { return Api.Update.parse_updateDialogFilters($0) } dict[1852826908] = { return Api.Update.parse_updateDialogPinned($0) } - dict[-513517117] = { return Api.Update.parse_updateDialogUnreadMark($0) } - dict[457829485] = { return Api.Update.parse_updateDraftMessage($0) } + dict[-1235684802] = { return Api.Update.parse_updateDialogUnreadMark($0) } + dict[-302247650] = { return Api.Update.parse_updateDraftMessage($0) } dict[457133559] = { return Api.Update.parse_updateEditChannelMessage($0) } dict[-469536605] = { return Api.Update.parse_updateEditMessage($0) } dict[386986326] = { return Api.Update.parse_updateEncryptedChatTyping($0) } @@ -1123,6 +1124,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1667805217] = { return Api.Update.parse_updateReadHistoryInbox($0) } dict[791617983] = { return Api.Update.parse_updateReadHistoryOutbox($0) } dict[-131960447] = { return Api.Update.parse_updateReadMessagesContents($0) } + dict[-1124907246] = { return Api.Update.parse_updateReadMonoForumInbox($0) } + dict[-1532521610] = { return Api.Update.parse_updateReadMonoForumOutbox($0) } dict[-145845461] = { return Api.Update.parse_updateReadStories($0) } dict[821314523] = { return Api.Update.parse_updateRecentEmojiStatuses($0) } dict[1870160884] = { return Api.Update.parse_updateRecentReactions($0) } diff --git a/submodules/TelegramApi/Sources/Api23.swift b/submodules/TelegramApi/Sources/Api23.swift index cbfbf62bbb..2915ff9ee4 100644 --- a/submodules/TelegramApi/Sources/Api23.swift +++ b/submodules/TelegramApi/Sources/Api23.swift @@ -47,11 +47,24 @@ public extension Api { } } public extension Api { - enum SavedDialog: TypeConstructorDescription { + indirect enum SavedDialog: TypeConstructorDescription { + case monoForumDialog(flags: Int32, peer: Api.Peer, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, draft: Api.DraftMessage?) case savedDialog(flags: Int32, peer: Api.Peer, topMessage: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { + case .monoForumDialog(let flags, let peer, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let draft): + if boxed { + buffer.appendInt32(2099641667) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(topMessage, buffer: buffer, boxed: false) + serializeInt32(readInboxMaxId, buffer: buffer, boxed: false) + serializeInt32(readOutboxMaxId, buffer: buffer, boxed: false) + serializeInt32(unreadCount, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {draft!.serialize(buffer, true)} + break case .savedDialog(let flags, let peer, let topMessage): if boxed { buffer.appendInt32(-1115174036) @@ -65,11 +78,46 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { + case .monoForumDialog(let flags, let peer, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let draft): + return ("monoForumDialog", [("flags", flags as Any), ("peer", peer as Any), ("topMessage", topMessage as Any), ("readInboxMaxId", readInboxMaxId as Any), ("readOutboxMaxId", readOutboxMaxId as Any), ("unreadCount", unreadCount as Any), ("draft", draft as Any)]) case .savedDialog(let flags, let peer, let topMessage): return ("savedDialog", [("flags", flags as Any), ("peer", peer as Any), ("topMessage", topMessage as Any)]) } } + public static func parse_monoForumDialog(_ reader: BufferReader) -> SavedDialog? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Api.DraftMessage? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.DraftMessage + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.SavedDialog.monoForumDialog(flags: _1!, peer: _2!, topMessage: _3!, readInboxMaxId: _4!, readOutboxMaxId: _5!, unreadCount: _6!, draft: _7) + } + else { + return nil + } + } public static func parse_savedDialog(_ reader: BufferReader) -> SavedDialog? { var _1: Int32? _1 = reader.readInt32() diff --git a/submodules/TelegramApi/Sources/Api26.swift b/submodules/TelegramApi/Sources/Api26.swift index 1fde48eb12..f7af9785e5 100644 --- a/submodules/TelegramApi/Sources/Api26.swift +++ b/submodules/TelegramApi/Sources/Api26.swift @@ -1037,8 +1037,8 @@ public extension Api { case updateDialogFilterOrder(order: [Int32]) case updateDialogFilters case updateDialogPinned(flags: Int32, folderId: Int32?, peer: Api.DialogPeer) - case updateDialogUnreadMark(flags: Int32, peer: Api.DialogPeer) - case updateDraftMessage(flags: Int32, peer: Api.Peer, topMsgId: Int32?, draft: Api.DraftMessage) + case updateDialogUnreadMark(flags: Int32, peer: Api.DialogPeer, savedPeerId: Api.Peer?) + case updateDraftMessage(flags: Int32, peer: Api.Peer, topMsgId: Int32?, savedPeerId: Api.Peer?, draft: Api.DraftMessage) case updateEditChannelMessage(message: Api.Message, pts: Int32, ptsCount: Int32) case updateEditMessage(message: Api.Message, pts: Int32, ptsCount: Int32) case updateEncryptedChatTyping(chatId: Int32) @@ -1096,6 +1096,8 @@ public extension Api { case updateReadHistoryInbox(flags: Int32, folderId: Int32?, peer: Api.Peer, maxId: Int32, stillUnreadCount: Int32, pts: Int32, ptsCount: Int32) case updateReadHistoryOutbox(peer: Api.Peer, maxId: Int32, pts: Int32, ptsCount: Int32) case updateReadMessagesContents(flags: Int32, messages: [Int32], pts: Int32, ptsCount: Int32, date: Int32?) + case updateReadMonoForumInbox(flags: Int32, channelId: Int64, savedPeerId: Api.Peer, readMaxId: Int32) + case updateReadMonoForumOutbox(channelId: Int64, savedPeerId: Api.Peer, readMaxId: Int32) case updateReadStories(peer: Api.Peer, maxId: Int32) case updateRecentEmojiStatuses case updateRecentReactions @@ -1653,20 +1655,22 @@ public extension Api { if Int(flags) & Int(1 << 1) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} peer.serialize(buffer, true) break - case .updateDialogUnreadMark(let flags, let peer): + case .updateDialogUnreadMark(let flags, let peer, let savedPeerId): if boxed { - buffer.appendInt32(-513517117) + buffer.appendInt32(-1235684802) } serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 {savedPeerId!.serialize(buffer, true)} break - case .updateDraftMessage(let flags, let peer, let topMsgId, let draft): + case .updateDraftMessage(let flags, let peer, let topMsgId, let savedPeerId, let draft): if boxed { - buffer.appendInt32(457829485) + buffer.appendInt32(-302247650) } serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {savedPeerId!.serialize(buffer, true)} draft.serialize(buffer, true) break case .updateEditChannelMessage(let message, let pts, let ptsCount): @@ -2164,6 +2168,23 @@ public extension Api { serializeInt32(ptsCount, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt32(date!, buffer: buffer, boxed: false)} break + case .updateReadMonoForumInbox(let flags, let channelId, let savedPeerId, let readMaxId): + if boxed { + buffer.appendInt32(-1124907246) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(channelId, buffer: buffer, boxed: false) + savedPeerId.serialize(buffer, true) + serializeInt32(readMaxId, buffer: buffer, boxed: false) + break + case .updateReadMonoForumOutbox(let channelId, let savedPeerId, let readMaxId): + if boxed { + buffer.appendInt32(-1532521610) + } + serializeInt64(channelId, buffer: buffer, boxed: false) + savedPeerId.serialize(buffer, true) + serializeInt32(readMaxId, buffer: buffer, boxed: false) + break case .updateReadStories(let peer, let maxId): if boxed { buffer.appendInt32(-145845461) @@ -2491,10 +2512,10 @@ public extension Api { return ("updateDialogFilters", []) case .updateDialogPinned(let flags, let folderId, let peer): return ("updateDialogPinned", [("flags", flags as Any), ("folderId", folderId as Any), ("peer", peer as Any)]) - case .updateDialogUnreadMark(let flags, let peer): - return ("updateDialogUnreadMark", [("flags", flags as Any), ("peer", peer as Any)]) - case .updateDraftMessage(let flags, let peer, let topMsgId, let draft): - return ("updateDraftMessage", [("flags", flags as Any), ("peer", peer as Any), ("topMsgId", topMsgId as Any), ("draft", draft as Any)]) + case .updateDialogUnreadMark(let flags, let peer, let savedPeerId): + return ("updateDialogUnreadMark", [("flags", flags as Any), ("peer", peer as Any), ("savedPeerId", savedPeerId as Any)]) + case .updateDraftMessage(let flags, let peer, let topMsgId, let savedPeerId, let draft): + return ("updateDraftMessage", [("flags", flags as Any), ("peer", peer as Any), ("topMsgId", topMsgId as Any), ("savedPeerId", savedPeerId as Any), ("draft", draft as Any)]) case .updateEditChannelMessage(let message, let pts, let ptsCount): return ("updateEditChannelMessage", [("message", message as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) case .updateEditMessage(let message, let pts, let ptsCount): @@ -2609,6 +2630,10 @@ public extension Api { return ("updateReadHistoryOutbox", [("peer", peer as Any), ("maxId", maxId as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) case .updateReadMessagesContents(let flags, let messages, let pts, let ptsCount, let date): return ("updateReadMessagesContents", [("flags", flags as Any), ("messages", messages as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any), ("date", date as Any)]) + case .updateReadMonoForumInbox(let flags, let channelId, let savedPeerId, let readMaxId): + return ("updateReadMonoForumInbox", [("flags", flags as Any), ("channelId", channelId as Any), ("savedPeerId", savedPeerId as Any), ("readMaxId", readMaxId as Any)]) + case .updateReadMonoForumOutbox(let channelId, let savedPeerId, let readMaxId): + return ("updateReadMonoForumOutbox", [("channelId", channelId as Any), ("savedPeerId", savedPeerId as Any), ("readMaxId", readMaxId as Any)]) case .updateReadStories(let peer, let maxId): return ("updateReadStories", [("peer", peer as Any), ("maxId", maxId as Any)]) case .updateRecentEmojiStatuses: @@ -3783,10 +3808,15 @@ public extension Api { if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.DialogPeer } + var _3: Api.Peer? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer + } } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateDialogUnreadMark(flags: _1!, peer: _2!) + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateDialogUnreadMark(flags: _1!, peer: _2!, savedPeerId: _3) } else { return nil @@ -3801,16 +3831,21 @@ public extension Api { } var _3: Int32? if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } - var _4: Api.DraftMessage? + var _4: Api.Peer? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } } + var _5: Api.DraftMessage? if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.DraftMessage + _5 = Api.parse(reader, signature: signature) as? Api.DraftMessage } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateDraftMessage(flags: _1!, peer: _2!, topMsgId: _3, draft: _4!) + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateDraftMessage(flags: _1!, peer: _2!, topMsgId: _3, savedPeerId: _4, draft: _5!) } else { return nil @@ -4802,6 +4837,47 @@ public extension Api { return nil } } + public static func parse_updateReadMonoForumInbox(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Api.Peer? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateReadMonoForumInbox(flags: _1!, channelId: _2!, savedPeerId: _3!, readMaxId: _4!) + } + else { + return nil + } + } + public static func parse_updateReadMonoForumOutbox(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateReadMonoForumOutbox(channelId: _1!, savedPeerId: _2!, readMaxId: _3!) + } + else { + return nil + } + } public static func parse_updateReadStories(_ reader: BufferReader) -> Update? { var _1: Api.Peer? if let signature = reader.readInt32() { diff --git a/submodules/TelegramApi/Sources/Api38.swift b/submodules/TelegramApi/Sources/Api38.swift index 89e642222c..490b54cdbd 100644 --- a/submodules/TelegramApi/Sources/Api38.swift +++ b/submodules/TelegramApi/Sources/Api38.swift @@ -5983,11 +5983,12 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func getDialogUnreadMarks() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.DialogPeer]>) { + static func getDialogUnreadMarks(flags: Int32, parentPeer: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.DialogPeer]>) { let buffer = Buffer() - buffer.appendInt32(585256482) - - return (FunctionDescription(name: "messages.getDialogUnreadMarks", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.DialogPeer]? in + buffer.appendInt32(555754018) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {parentPeer!.serialize(buffer, true)} + return (FunctionDescription(name: "messages.getDialogUnreadMarks", parameters: [("flags", String(describing: flags)), ("parentPeer", String(describing: parentPeer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.DialogPeer]? in let reader = BufferReader(buffer) var result: [Api.DialogPeer]? if let _ = reader.readInt32() { @@ -7281,12 +7282,13 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func markDialogUnread(flags: Int32, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func markDialogUnread(flags: Int32, parentPeer: Api.InputPeer?, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-1031349873) + buffer.appendInt32(-1940912392) serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {parentPeer!.serialize(buffer, true)} peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.markDialogUnread", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + return (FunctionDescription(name: "messages.markDialogUnread", parameters: [("flags", String(describing: flags)), ("parentPeer", String(describing: parentPeer)), ("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in let reader = BufferReader(buffer) var result: Api.Bool? if let signature = reader.readInt32() { @@ -7470,6 +7472,23 @@ public extension Api.functions.messages { }) } } +public extension Api.functions.messages { + static func readSavedHistory(parentPeer: Api.InputPeer, peer: Api.InputPeer, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1169540261) + parentPeer.serialize(buffer, true) + peer.serialize(buffer, true) + serializeInt32(maxId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.readSavedHistory", parameters: [("parentPeer", String(describing: parentPeer)), ("peer", String(describing: peer)), ("maxId", String(describing: maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.messages { static func receivedMessages(maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.ReceivedNotifyMessage]>) { let buffer = Buffer() diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index bc4b6190ed..6f2fe299c1 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -113,7 +113,7 @@ enum AccountStateMutationOperation { case SyncChatListFilters case UpdateChatListFilterOrder(order: [Int32]) case UpdateChatListFilter(id: Int32, filter: Api.DialogFilter?) - case UpdateReadThread(threadMessageId: MessageId, readMaxId: Int32, isIncoming: Bool, mainChannelMessage: MessageId?) + case UpdateReadThread(peerId: PeerId, threadId: Int64, readMaxId: Int32, isIncoming: Bool, mainChannelMessage: MessageId?) case UpdateGroupCallParticipants(id: Int64, accessHash: Int64, participants: [Api.GroupCallParticipant], version: Int32) case UpdateGroupCall(peerId: PeerId?, call: Api.GroupCall) case UpdateGroupCallChainBlocks(id: Int64, accessHash: Int64, subChainId: Int32, blocks: [Data], nextOffset: Int32) @@ -122,7 +122,7 @@ enum AccountStateMutationOperation { case UpdateAudioTranscription(messageId: MessageId, id: Int64, isPending: Bool, text: String) case UpdateConfig case UpdateExtendedMedia(MessageId, [Api.MessageExtendedMedia]) - case ResetForumTopic(topicId: MessageId, data: StoreMessageHistoryThreadData, pts: Int32) + case ResetForumTopic(topicId: PeerAndBoundThreadId, data: StoreMessageHistoryThreadData, pts: Int32?) case UpdateStory(peerId: PeerId, story: Api.StoryItem) case UpdateReadStories(peerId: PeerId, maxId: Int32) case UpdateStoryStealthMode(data: Api.StoriesStealthMode) @@ -392,8 +392,8 @@ struct AccountMutableState { self.addOperation(.ReadOutbox(messageId, timestamp)) } - mutating func readThread(threadMessageId: MessageId, readMaxId: Int32, isIncoming: Bool, mainChannelMessage: MessageId?) { - self.addOperation(.UpdateReadThread(threadMessageId: threadMessageId, readMaxId: readMaxId, isIncoming: isIncoming, mainChannelMessage: mainChannelMessage)) + mutating func readThread(peerId: PeerId, threadId: Int64, readMaxId: Int32, isIncoming: Bool, mainChannelMessage: MessageId?) { + self.addOperation(.UpdateReadThread(peerId: peerId, threadId: threadId, readMaxId: readMaxId, isIncoming: isIncoming, mainChannelMessage: mainChannelMessage)) } mutating func updateGroupCallParticipants(id: Int64, accessHash: Int64, participants: [Api.GroupCallParticipant], version: Int32) { @@ -854,8 +854,8 @@ struct AccountReplayedFinalState { let updatedPeersNearby: [PeerNearby]? let isContactUpdates: [(PeerId, Bool)] let delayNotificatonsUntil: Int32? - let updatedIncomingThreadReadStates: [MessageId: MessageId.Id] - let updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] + let updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] + let updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] let updateConfig: Bool let isPremiumUpdated: Bool let updatedRevenueBalances: [PeerId: RevenueStats.Balances] @@ -887,8 +887,8 @@ struct AccountFinalStateEvents { let updatedQts: Int32? let externallyUpdatedPeerId: Set let authorizationListUpdated: Bool - let updatedIncomingThreadReadStates: [MessageId: MessageId.Id] - let updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] + let updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] + let updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] let updateConfig: Bool let isPremiumUpdated: Bool let updatedRevenueBalances: [PeerId: RevenueStats.Balances] @@ -901,7 +901,7 @@ struct AccountFinalStateEvents { return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedRevenueBalances.isEmpty && self.updatedStarsBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty && self.addedConferenceInvitationMessagesIds.isEmpty } - init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedRevenueBalances: [PeerId: RevenueStats.Balances] = [:], updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set = Set(), reportMessageDelivery: Set = Set(), addedConferenceInvitationMessagesIds: [MessageId] = []) { + init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedRevenueBalances: [PeerId: RevenueStats.Balances] = [:], updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set = Set(), reportMessageDelivery: Set = Set(), addedConferenceInvitationMessagesIds: [MessageId] = []) { self.addedIncomingMessageIds = addedIncomingMessageIds self.addedReactionEvents = addedReactionEvents self.wasScheduledMessageIds = wasScheduledMessageIds diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index bc5d8b6b3e..b880eddbb4 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -234,13 +234,13 @@ func _internal_createForumChannelTopic(account: Account, peerId: PeerId, title: } } - if let topicId = topicId { + if let topicId { return account.postbox.transaction { transaction -> Void in transaction.removeHole(peerId: peerId, threadId: topicId, namespace: Namespaces.Message.Cloud, space: .everywhere, range: 1 ... (Int32.max - 1)) } |> castError(CreateForumChannelTopicError.self) |> mapToSignal { _ -> Signal in - return resolveForumThreads(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, ids: [MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: topicId))]) + return resolveForumThreads(accountPeerId: account.peerId, postbox: account.postbox, source: .network(account.network), ids: [PeerAndBoundThreadId(peerId: peerId, threadId: topicId)]) |> castError(CreateForumChannelTopicError.self) |> map { _ -> Int64 in return topicId @@ -270,7 +270,7 @@ func _internal_fetchForumChannelTopic(account: Account, peerId: PeerId, threadId if let info = info { return .single(.result(info)) } else { - return .single(.progress) |> then(resolveForumThreads(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, ids: [MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))]) + return .single(.progress) |> then(resolveForumThreads(accountPeerId: account.peerId, postbox: account.postbox, source: .network(account.network), ids: [PeerAndBoundThreadId(peerId: peerId, threadId: threadId)]) |> mapToSignal { _ -> Signal in return account.postbox.transaction { transaction -> FetchForumChannelTopicResult in if let data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { @@ -803,6 +803,58 @@ func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Post } } + items.append(LoadMessageHistoryThreadsResult.Item( + threadId: peer.peerId.toInt64(), + data: data, + topMessage: topMessage, + unreadMentionsCount: 0, + unreadReactionsCount: 0, + index: topicIndex, + threadPeer: threadPeer + )) + case let .monoForumDialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _): + let data = MessageHistoryThreadData( + creationDate: 0, + isOwnedByMe: true, + author: accountPeerId, + info: EngineMessageHistoryThread.Info( + title: "", + icon: nil, + iconColor: 0 + ), + incomingUnreadCount: unreadCount, + maxIncomingReadId: readInboxMaxId, + maxKnownMessageId: topMessage, + maxOutgoingReadId: readOutboxMaxId, + isClosed: false, + isHidden: false, + notificationSettings: TelegramPeerNotificationSettings.defaultSettings + ) + + var topTimestamp: Int32 = 1 + for message in addedMessages { + if message.id.peerId == peerId && message.threadId == peer.peerId.toInt64() { + topTimestamp = max(topTimestamp, message.timestamp) + } + } + + let topicIndex = StoredPeerThreadCombinedState.Index(timestamp: topTimestamp, threadId: peer.peerId.toInt64(), messageId: topMessage) + if let minIndexValue = minIndex { + if topicIndex < minIndexValue { + minIndex = topicIndex + } + } else { + minIndex = topicIndex + } + + var threadPeer: Peer? + for user in users { + if user.peerId == peer.peerId { + threadPeer = TelegramUser(user: user) + break + } + } + items.append(LoadMessageHistoryThreadsResult.Item( threadId: peer.peerId.toInt64(), data: data, diff --git a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift index 360e066bee..8d8ea821a6 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift @@ -413,7 +413,11 @@ public func resendMessages(account: Account, messageIds: [MessageId]) -> Signal< } else if let channel = peer as? TelegramChannel { if channel.flags.contains(.isCreator) || channel.adminRights != nil { } else { - sendPaidMessageStars = channel.sendPaidMessageStars + if channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = transaction.getPeer(linkedMonoforumId) as? TelegramChannel { + sendPaidMessageStars = mainChannel.sendPaidMessageStars + } else { + sendPaidMessageStars = channel.sendPaidMessageStars + } } } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 4c108d8c83..8378d92a23 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -130,12 +130,12 @@ private func peerIdsRequiringLocalChatStateFromUpdates(_ updates: [Api.Update]) peerIds.insert(PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))) case let .updateReadHistoryInbox(_, _, peer, _, _, _, _): peerIds.insert(peer.peerId) - case let .updateDraftMessage(_, peer, _, draft): - switch draft { - case .draftMessage: - peerIds.insert(peer.peerId) - case .draftMessageEmpty: - break + case let .updateDraftMessage(_, peer, _, _, draft): + switch draft { + case .draftMessage: + peerIds.insert(peer.peerId) + case .draftMessageEmpty: + break } default: break @@ -1213,16 +1213,25 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: if let mainChannelId = mainChannelId, let mainChannelPost = mainChannelPost { mainChannelMessage = MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(mainChannelId)), namespace: Namespaces.Message.Cloud, id: mainChannelPost) } - updatedState.readThread(threadMessageId: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: topMsgId), readMaxId: readMaxId, isIncoming: true, mainChannelMessage: mainChannelMessage) + updatedState.readThread(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), threadId: Int64(topMsgId), readMaxId: readMaxId, isIncoming: true, mainChannelMessage: mainChannelMessage) case let .updateReadChannelDiscussionOutbox(channelId, topMsgId, readMaxId): - updatedState.readThread(threadMessageId: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: topMsgId), readMaxId: readMaxId, isIncoming: false, mainChannelMessage: nil) - case let .updateDialogUnreadMark(flags, peer): + updatedState.readThread(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), threadId: Int64(topMsgId), readMaxId: readMaxId, isIncoming: false, mainChannelMessage: nil) + case let .updateReadMonoForumInbox(_, channelId, savedPeerId, readMaxId): + updatedState.readThread(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), threadId: savedPeerId.peerId.toInt64(), readMaxId: readMaxId, isIncoming: true, mainChannelMessage: nil) + case let .updateReadMonoForumOutbox(channelId, savedPeerId, readMaxId): + updatedState.readThread(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), threadId: savedPeerId.peerId.toInt64(), readMaxId: readMaxId, isIncoming: false, mainChannelMessage: nil) + case let .updateDialogUnreadMark(flags, peer, savedPeerId): switch peer { - case let .dialogPeer(peer): - let peerId = peer.peerId + case let .dialogPeer(peer): + let peerId = peer.peerId + //TODO:release + if let savedPeerId { + let _ = savedPeerId + } else { updatedState.updatePeerChatUnreadMark(peerId, namespace: Namespaces.Message.Cloud, value: (flags & (1 << 0)) != 0) - case .dialogPeerFolder: - break + } + case .dialogPeerFolder: + break } case let .updateWebPage(apiWebpage, _, _): switch apiWebpage { @@ -1563,7 +1572,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: updatedState.addUpdateInstalledStickerPacks(.sync) case .updateSavedGifs: updatedState.addUpdateRecentGifs() - case let .updateDraftMessage(_, peer, topMsgId, draft): + case let .updateDraftMessage(_, peer, topMsgId, savedPeerId, draft): let inputState: SynchronizeableChatInputState? switch draft { case .draftMessageEmpty: @@ -1620,7 +1629,9 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: inputState = SynchronizeableChatInputState(replySubject: replySubject, text: message, entities: messageTextEntitiesFromApiEntities(entities ?? []), timestamp: date, textSelection: nil, messageEffectId: messageEffectId) } var threadId: Int64? - if let topMsgId = topMsgId { + if let savedPeerId { + threadId = savedPeerId.peerId.toInt64() + } else if let topMsgId { threadId = Int64(topMsgId) } updatedState.addUpdateChatInputState(peerId: peer.peerId, threadId: threadId, state: inputState) @@ -1905,7 +1916,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: } } - return resolveForumThreads(accountPeerId: accountPeerId, postbox: postbox, network: network, state: finalState) + return resolveForumThreads(accountPeerId: accountPeerId, postbox: postbox, source: .network(network), state: finalState) |> mapToSignal { finalState in return resolveAssociatedMessages(accountPeerId: accountPeerId, postbox: postbox, network: network, state: finalState) |> mapToSignal { resultingState -> Signal in @@ -1921,8 +1932,52 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: } } -func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, network: Network, state: AccountMutableState) -> Signal { - var forumThreadIds = Set() +final class FetchedForumThreads { + enum Item { + case savedDialog(Api.SavedDialog) + case forum(Api.ForumTopic) + } + + let items: [Item] + let totalCount: Int + let orderByDate: Bool + let pts: Int32? + let messages: [Api.Message] + let users: [Api.User] + let chats: [Api.Chat] + + init(items: [Item], totalCount: Int, orderByDate: Bool, pts: Int32?, messages: [Api.Message], users: [Api.User], chats: [Api.Chat]) { + self.items = items + self.totalCount = totalCount + self.orderByDate = orderByDate + self.pts = pts + self.messages = messages + self.users = users + self.chats = chats + } + + convenience init(forumTopics: Api.messages.ForumTopics) { + switch forumTopics { + case let .forumTopics(flags, count, topics, messages, chats, users, pts): + let orderByDate = (flags & (1 << 0)) != 0 + self.init(items: topics.map(Item.forum), totalCount: Int(count), orderByDate: orderByDate, pts: pts, messages: messages, users: users, chats: chats) + } + } + + convenience init(savedDialogs: Api.messages.SavedDialogs) { + switch savedDialogs { + case let .savedDialogs(dialogs, messages, chats, users): + self.init(items: dialogs.map(Item.savedDialog), totalCount: Int(dialogs.count), orderByDate: false, pts: nil, messages: messages, users: users, chats: chats) + case let .savedDialogsSlice(count, dialogs, messages, chats, users): + self.init(items: dialogs.map(Item.savedDialog), totalCount: Int(count), orderByDate: false, pts: nil, messages: messages, users: users, chats: chats) + case .savedDialogsNotModified: + self.init(items: [], totalCount: 0, orderByDate: false, pts: 0, messages: [], users: [], chats: []) + } + } +} + +func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchMessageHistoryHoleSource, state: AccountMutableState) -> Signal { + var forumThreadIds = Set() for operation in state.operations { switch operation { @@ -1930,8 +1985,8 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, network: Netwo for message in messages { if let threadId = message.threadId { if let channel = state.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { - if channel.flags.contains(.isForum) { - forumThreadIds.insert(MessageId(peerId: message.id.peerId, namespace: message.id.namespace, id: Int32(clamping: threadId))) + if channel.flags.contains(.isForum) || channel.flags.contains(.isMonoforum) { + forumThreadIds.insert(PeerAndBoundThreadId(peerId: message.id.peerId, threadId: threadId)) } } } @@ -1945,31 +2000,65 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, network: Netwo return .single(state) } else { return postbox.transaction { transaction -> Signal in - var missingForumThreadIds: [PeerId: [Int32]] = [:] + var missingForumThreadIds: [PeerId: [Int64]] = [:] for threadId in forumThreadIds { - if let _ = transaction.getMessageHistoryThreadInfo(peerId: threadId.peerId, threadId: Int64(threadId.id)) { + if let _ = transaction.getMessageHistoryThreadInfo(peerId: threadId.peerId, threadId: threadId.threadId) { } else { - missingForumThreadIds[threadId.peerId, default: []].append(threadId.id) + missingForumThreadIds[threadId.peerId, default: []].append(threadId.threadId) } } if missingForumThreadIds.isEmpty { return .single(state) } else { - var signals: [Signal<(Peer, Api.messages.ForumTopics)?, NoError>] = [] + var signals: [Signal<(Peer, FetchedForumThreads)?, NoError>] = [] for (peerId, threadIds) in missingForumThreadIds { - guard let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) else { + guard let peer = transaction.getPeer(peerId) as? TelegramChannel, let inputPeer = apiInputPeer(peer), let inputChannel = apiInputChannel(peer) else { Logger.shared.log("State", "can't fetch thread infos \(threadIds) for peer \(peerId): can't create inputChannel") continue } - let signal = network.request(Api.functions.channels.getForumTopicsByID(channel: inputChannel, topics: threadIds)) - |> map { result -> (Peer, Api.messages.ForumTopics)? in - return (peer, result) + + if peer.flags.contains(.isMonoforum) { + //TODO:release + let signal = source.request(Api.functions.messages.getSavedDialogs(flags: 1 << 1, parentPeer: inputPeer, offsetDate: 0, offsetId: 0, offsetPeer: .inputPeerEmpty, limit: 100, hash: 0)) + |> map { result -> (Peer, FetchedForumThreads)? in + let result = FetchedForumThreads(savedDialogs: result) + return (peer, FetchedForumThreads( + items: result.items.filter({ item -> Bool in + switch item { + case let .savedDialog(savedDialog): + switch savedDialog { + case let .monoForumDialog(_, peer, _, _, _, _, _): + return threadIds.contains(peer.peerId.toInt64()) + case .savedDialog: + return false + } + case .forum: + return false + } + }), + totalCount: result.totalCount, + orderByDate: result.orderByDate, + pts: result.pts, + messages: result.messages, + users: result.users, + chats: result.chats + )) + } + |> `catch` { _ -> Signal<(Peer, FetchedForumThreads)?, NoError> in + return .single(nil) + } + signals.append(signal) + } else { + let signal = source.request(Api.functions.channels.getForumTopicsByID(channel: inputChannel, topics: threadIds.map { Int32(clamping: $0) })) + |> map { result -> (Peer, FetchedForumThreads)? in + return (peer, FetchedForumThreads(forumTopics: result)) + } + |> `catch` { _ -> Signal<(Peer, FetchedForumThreads)?, NoError> in + return .single(nil) + } + signals.append(signal) } - |> `catch` { _ -> Signal<(Peer, Api.messages.ForumTopics)?, NoError> in - return .single(nil) - } - signals.append(signal) } return combineLatest(signals) @@ -1983,24 +2072,24 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, network: Netwo let peerIsForum = peer.isForum let peerId = peer.id - switch result { - case let .forumTopics(_, _, topics, messages, chats, users, pts): - state.mergeChats(chats) - state.mergeUsers(users) - - for message in messages { - if let message = StoreMessage(apiMessage: message, accountPeerId: accountPeerId, peerIsForum: peerIsForum) { - storeMessages.append(message) - } + state.mergeChats(result.chats) + state.mergeUsers(result.users) + + for message in result.messages { + if let message = StoreMessage(apiMessage: message, accountPeerId: accountPeerId, peerIsForum: peerIsForum) { + storeMessages.append(message) } - - for topic in topics { + } + + for topic in result.items { + switch topic { + case let .forum(topic): switch topic { case let .forumTopic(flags, id, date, title, iconColor, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, fromId, notifySettings, draft): let _ = draft state.operations.append(.ResetForumTopic( - topicId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id), + topicId: PeerAndBoundThreadId(peerId: peerId, threadId: Int64(id)), data: StoreMessageHistoryThreadData( data: MessageHistoryThreadData( creationDate: date, @@ -2023,11 +2112,44 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, network: Netwo unreadMentionCount: unreadMentionsCount, unreadReactionCount: unreadReactionsCount ), - pts: pts + pts: result.pts )) case .forumTopicDeleted: break } + case let .savedDialog(savedDialog): + switch savedDialog { + case let .monoForumDialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _): + + state.operations.append(.ResetForumTopic( + topicId: PeerAndBoundThreadId(peerId: peerId, threadId: peer.peerId.toInt64()), + data: StoreMessageHistoryThreadData( + data: MessageHistoryThreadData( + creationDate: 0, + isOwnedByMe: true, + author: accountPeerId, + info: EngineMessageHistoryThread.Info( + title: "", + icon: nil, + iconColor: 0 + ), + incomingUnreadCount: unreadCount, + maxIncomingReadId: readInboxMaxId, + maxKnownMessageId: topMessage, + maxOutgoingReadId: readOutboxMaxId, + isClosed: false, + isHidden: false, + notificationSettings: TelegramPeerNotificationSettings.defaultSettings + ), + topMessageId: topMessage, + unreadMentionCount: 0, + unreadReactionCount: 0 + ), + pts: result.pts + )) + case .savedDialog: + break + } } } } @@ -2043,38 +2165,71 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, network: Netwo } } -func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, network: Network, ids: [MessageId]) -> Signal { +func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchMessageHistoryHoleSource, ids: [PeerAndBoundThreadId]) -> Signal { let forumThreadIds = Set(ids) if forumThreadIds.isEmpty { return .single(Void()) } else { return postbox.transaction { transaction -> Signal in - var missingForumThreadIds: [PeerId: [Int32]] = [:] + var missingForumThreadIds: [PeerId: [Int64]] = [:] for threadId in forumThreadIds { - if let _ = transaction.getMessageHistoryThreadInfo(peerId: threadId.peerId, threadId: Int64(threadId.id)) { + if let _ = transaction.getMessageHistoryThreadInfo(peerId: threadId.peerId, threadId: threadId.threadId) { } else { - missingForumThreadIds[threadId.peerId, default: []].append(threadId.id) + missingForumThreadIds[threadId.peerId, default: []].append(threadId.threadId) } } if missingForumThreadIds.isEmpty { return .single(Void()) } else { - var signals: [Signal<(Peer, Api.messages.ForumTopics)?, NoError>] = [] + var signals: [Signal<(Peer, FetchedForumThreads)?, NoError>] = [] for (peerId, threadIds) in missingForumThreadIds { - guard let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) else { + guard let peer = transaction.getPeer(peerId) as? TelegramChannel, let inputPeer = apiInputPeer(peer), let inputChannel = apiInputChannel(peer) else { Logger.shared.log("State", "can't fetch thread infos \(threadIds) for peer \(peerId): can't create inputChannel") continue } - let signal = network.request(Api.functions.channels.getForumTopicsByID(channel: inputChannel, topics: threadIds)) - |> map { result -> (Peer, Api.messages.ForumTopics)? in - return (peer, result) + + if peer.flags.contains(.isMonoforum) { + let signal = source.request(Api.functions.messages.getSavedDialogs(flags: 1 << 1, parentPeer: inputPeer, offsetDate: 0, offsetId: 0, offsetPeer: .inputPeerEmpty, limit: 100, hash: 0)) + |> map { result -> (Peer, FetchedForumThreads)? in + let result = FetchedForumThreads(savedDialogs: result) + return (peer, FetchedForumThreads( + items: result.items.filter({ item -> Bool in + switch item { + case let .savedDialog(savedDialog): + switch savedDialog { + case let .monoForumDialog(_, peer, _, _, _, _, _): + return threadIds.contains(peer.peerId.toInt64()) + case .savedDialog: + return false + } + case .forum: + return false + } + }), + totalCount: result.totalCount, + orderByDate: result.orderByDate, + pts: result.pts, + messages: result.messages, + users: result.users, + chats: result.chats + )) + } + |> `catch` { _ -> Signal<(Peer, FetchedForumThreads)?, NoError> in + return .single(nil) + } + signals.append(signal) + } else { + let signal = source.request(Api.functions.channels.getForumTopicsByID(channel: inputChannel, topics: threadIds.map { Int32(clamping: $0) })) + |> map { result -> (Peer, FetchedForumThreads)? in + return (peer, FetchedForumThreads(forumTopics: result)) + } + |> `catch` { _ -> Signal<(Peer, FetchedForumThreads)?, NoError> in + return .single(nil) + } + signals.append(signal) } - |> `catch` { _ -> Signal<(Peer, Api.messages.ForumTopics)?, NoError> in - return .single(nil) - } - signals.append(signal) } return combineLatest(signals) @@ -2089,18 +2244,18 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, network: Netwo let peerIsForum = peer.isForum let peerId = peer.id - switch result { - case let .forumTopics(_, _, topics, messages, apiChats, apiUsers, _): - chats.append(contentsOf: apiChats) - users.append(contentsOf: apiUsers) - - for message in messages { - if let message = StoreMessage(apiMessage: message, accountPeerId: accountPeerId, peerIsForum: peerIsForum) { - storeMessages.append(message) - } + chats.append(contentsOf: result.chats) + users.append(contentsOf: result.users) + + for message in result.messages { + if let message = StoreMessage(apiMessage: message, accountPeerId: accountPeerId, peerIsForum: peerIsForum) { + storeMessages.append(message) } - - for topic in topics { + } + + for item in result.items { + switch item { + case let .forum(topic): switch topic { case let .forumTopic(flags, id, date, title, iconColor, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, fromId, notifySettings, draft): let _ = draft @@ -2131,6 +2286,35 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, network: Netwo case .forumTopicDeleted: break } + case let .savedDialog(savedDialog): + switch savedDialog { + case let .monoForumDialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _): + let data = MessageHistoryThreadData( + creationDate: 0, + isOwnedByMe: true, + author: accountPeerId, + info: EngineMessageHistoryThread.Info( + title: "", + icon: nil, + iconColor: 0 + ), + incomingUnreadCount: unreadCount, + maxIncomingReadId: readInboxMaxId, + maxKnownMessageId: topMessage, + maxOutgoingReadId: readOutboxMaxId, + isClosed: false, + isHidden: false, + notificationSettings: TelegramPeerNotificationSettings.defaultSettings + ) + if let entry = StoredMessageHistoryThreadInfo(data) { + transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: peer.peerId.toInt64(), info: entry) + } + + transaction.replaceMessageTagSummary(peerId: peerId, threadId: peer.peerId.toInt64(), tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, customTag: nil, count: 0, maxId: topMessage) + transaction.replaceMessageTagSummary(peerId: peerId, threadId: peer.peerId.toInt64(), tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, customTag: nil, count: 0, maxId: topMessage) + case .savedDialog: + break + } } } } @@ -2148,13 +2332,13 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, network: Netwo } } -func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, network: Network, fetchedChatList: FetchedChatList) -> Signal { - var forumThreadIds = Set() +func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchMessageHistoryHoleSource, fetchedChatList: FetchedChatList) -> Signal { + var forumThreadIds = Set() for message in fetchedChatList.storeMessages { if let threadId = message.threadId { - if let channel = fetchedChatList.peers.peers.first(where: { $0.key == message.id.peerId })?.value as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isForum) { - forumThreadIds.insert(MessageId(peerId: message.id.peerId, namespace: message.id.namespace, id: Int32(clamping: threadId))) + if let channel = fetchedChatList.peers.peers.first(where: { $0.key == message.id.peerId })?.value as? TelegramChannel, case .group = channel.info, (channel.flags.contains(.isForum) || channel.flags.contains(.isMonoforum)) { + forumThreadIds.insert(PeerAndBoundThreadId(peerId: message.id.peerId, threadId: threadId)) } } } @@ -2163,31 +2347,65 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, network: Netwo return .single(fetchedChatList) } else { return postbox.transaction { transaction -> Signal in - var missingForumThreadIds: [PeerId: [Int32]] = [:] + var missingForumThreadIds: [PeerId: [Int64]] = [:] for threadId in forumThreadIds { - if let _ = transaction.getMessageHistoryThreadInfo(peerId: threadId.peerId, threadId: Int64(threadId.id)) { + if let _ = transaction.getMessageHistoryThreadInfo(peerId: threadId.peerId, threadId: threadId.threadId) { } else { - missingForumThreadIds[threadId.peerId, default: []].append(threadId.id) + missingForumThreadIds[threadId.peerId, default: []].append(threadId.threadId) } } if missingForumThreadIds.isEmpty { return .single(fetchedChatList) } else { - var signals: [Signal<(Peer, Api.messages.ForumTopics)?, NoError>] = [] + var signals: [Signal<(Peer, FetchedForumThreads)?, NoError>] = [] for (peerId, threadIds) in missingForumThreadIds { - guard let peer = fetchedChatList.peers.get(peerId), let inputChannel = apiInputChannel(peer) else { + guard let peer = fetchedChatList.peers.get(peerId) as? TelegramChannel, let inputPeer = apiInputPeer(peer), let inputChannel = apiInputChannel(peer) else { Logger.shared.log("resolveForumThreads", "can't fetch thread infos \(threadIds) for peer \(peerId): can't create inputChannel") continue } - let signal = network.request(Api.functions.channels.getForumTopicsByID(channel: inputChannel, topics: threadIds)) - |> map { result -> (Peer, Api.messages.ForumTopics)? in - return (peer, result) + + if peer.flags.contains(.isMonoforum) { + //TODO:release + let signal = source.request(Api.functions.messages.getSavedDialogs(flags: 1 << 1, parentPeer: inputPeer, offsetDate: 0, offsetId: 0, offsetPeer: .inputPeerEmpty, limit: 100, hash: 0)) + |> map { result -> (Peer, FetchedForumThreads)? in + let result = FetchedForumThreads(savedDialogs: result) + return (peer, FetchedForumThreads( + items: result.items.filter({ item -> Bool in + switch item { + case let .savedDialog(savedDialog): + switch savedDialog { + case let .monoForumDialog(_, peer, _, _, _, _, _): + return threadIds.contains(peer.peerId.toInt64()) + case .savedDialog: + return false + } + case .forum: + return false + } + }), + totalCount: result.totalCount, + orderByDate: result.orderByDate, + pts: result.pts, + messages: result.messages, + users: result.users, + chats: result.chats + )) + } + |> `catch` { _ -> Signal<(Peer, FetchedForumThreads)?, NoError> in + return .single(nil) + } + signals.append(signal) + } else { + let signal = source.request(Api.functions.channels.getForumTopicsByID(channel: inputChannel, topics: threadIds.map { Int32(clamping: $0) })) + |> map { result -> (Peer, FetchedForumThreads)? in + return (peer, FetchedForumThreads(forumTopics: result)) + } + |> `catch` { _ -> Signal<(Peer, FetchedForumThreads)?, NoError> in + return .single(nil) + } + signals.append(signal) } - |> `catch` { _ -> Signal<(Peer, Api.messages.ForumTopics)?, NoError> in - return .single(nil) - } - signals.append(signal) } return combineLatest(signals) @@ -2199,22 +2417,22 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, network: Netwo let peerIsForum = peer.isForum let peerId = peer.id - switch result { - case let .forumTopics(_, _, topics, messages, chats, users, _): - fetchedChatList.peers = fetchedChatList.peers.union(with: AccumulatedPeers(chats: chats, users: users)) - - for message in messages { - if let message = StoreMessage(apiMessage: message, accountPeerId: accountPeerId, peerIsForum: peerIsForum) { - fetchedChatList.storeMessages.append(message) - } + fetchedChatList.peers = fetchedChatList.peers.union(with: AccumulatedPeers(chats: result.chats, users: result.users)) + + for message in result.messages { + if let message = StoreMessage(apiMessage: message, accountPeerId: accountPeerId, peerIsForum: peerIsForum) { + fetchedChatList.storeMessages.append(message) } - - for topic in topics { + } + + for item in result.items { + switch item { + case let .forum(topic): switch topic { case let .forumTopic(flags, id, date, title, iconColor, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, fromId, notifySettings, draft): let _ = draft - fetchedChatList.threadInfos[MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id)] = StoreMessageHistoryThreadData( + fetchedChatList.threadInfos[PeerAndBoundThreadId(peerId: peerId, threadId: Int64(id))] = StoreMessageHistoryThreadData( data: MessageHistoryThreadData( creationDate: date, isOwnedByMe: (flags & (1 << 1)) != 0, @@ -2239,6 +2457,35 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, network: Netwo case .forumTopicDeleted: break } + case let .savedDialog(savedDialog): + switch savedDialog { + case let .monoForumDialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _): + + fetchedChatList.threadInfos[PeerAndBoundThreadId(peerId: peerId, threadId: peer.peerId.toInt64())] = StoreMessageHistoryThreadData( + data: MessageHistoryThreadData( + creationDate: 0, + isOwnedByMe: true, + author: accountPeerId, + info: EngineMessageHistoryThread.Info( + title: "", + icon: nil, + iconColor: 0 + ), + incomingUnreadCount: unreadCount, + maxIncomingReadId: readInboxMaxId, + maxKnownMessageId: topMessage, + maxOutgoingReadId: readOutboxMaxId, + isClosed: false, + isHidden: false, + notificationSettings: TelegramPeerNotificationSettings.defaultSettings + ), + topMessageId: topMessage, + unreadMentionCount: 0, + unreadReactionCount: 0 + ) + case .savedDialog: + break + } } } } @@ -2416,7 +2663,7 @@ private func resolveAssociatedMessages(accountPeerId: PeerId, postbox: Postbox, if missingReplyMessageIds.isEmpty && missingGeneralMessageIds.isEmpty { return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: messagesFromOperations(state: state), reactions: reactionsFromState(state), result: state) |> mapToSignal { state in - return resolveForumThreads(accountPeerId: accountPeerId, postbox: postbox, network: network, state: state) + return resolveForumThreads(accountPeerId: accountPeerId, postbox: postbox, source: .network(network), state: state) } } else { var missingPeers = false @@ -2518,7 +2765,7 @@ private func resolveAssociatedMessages(accountPeerId: PeerId, postbox: Postbox, |> mapToSignal { updatedState -> Signal in return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: messagesFromOperations(state: updatedState), reactions: reactionsFromState(updatedState), result: updatedState) |> mapToSignal { state in - return resolveForumThreads(accountPeerId: accountPeerId, postbox: postbox, network: network, state: state) + return resolveForumThreads(accountPeerId: accountPeerId, postbox: postbox, source: .network(network), state: state) } } } @@ -3118,7 +3365,7 @@ private func pollChannel(accountPeerId: PeerId, postbox: Postbox, network: Netwo } } - return resolveForumThreads(accountPeerId: accountPeerId, postbox: postbox, network: network, state: updatedState) + return resolveForumThreads(accountPeerId: accountPeerId, postbox: postbox, source: .network(network), state: updatedState) |> mapToSignal { updatedState in return resolveAssociatedStories(postbox: postbox, network: network, accountPeerId: accountPeerId, state: updatedState) |> map { updatedState -> (AccountMutableState, Bool, Int32?) in @@ -3443,8 +3690,8 @@ func replayFinalState( var peerIdsWithAddedSecretMessages = Set() var updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:] - var updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:] - var updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:] + var updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:] + var updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:] var updatedSecretChatTypingActivities = Set() var updatedWebpages: [MediaId: TelegramMediaWebpage] = [:] var updatedCalls: [Api.PhoneCall] = [] @@ -4018,28 +4265,29 @@ func replayFinalState( } case .ReadGroupFeedInbox: break - case let .UpdateReadThread(threadMessageId, readMaxId, isIncoming, mainChannelMessage): + case let .UpdateReadThread(peerId, threadId, readMaxId, isIncoming, mainChannelMessage): + let peerAndThreadId = PeerAndBoundThreadId(peerId: peerId, threadId: threadId) if isIncoming { - if let currentId = updatedIncomingThreadReadStates[threadMessageId] { + if let currentId = updatedIncomingThreadReadStates[peerAndThreadId] { if currentId < readMaxId { - updatedIncomingThreadReadStates[threadMessageId] = readMaxId + updatedIncomingThreadReadStates[peerAndThreadId] = readMaxId } } else { - updatedIncomingThreadReadStates[threadMessageId] = readMaxId + updatedIncomingThreadReadStates[peerAndThreadId] = readMaxId } - if let channel = transaction.getPeer(threadMessageId.peerId) as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isForum) { - let threadId = Int64(threadMessageId.id) - if var data = transaction.getMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { + if let channel = transaction.getPeer(peerAndThreadId.peerId) as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isForum) { + let threadId = peerAndThreadId.threadId + if var data = transaction.getMessageHistoryThreadInfo(peerId: peerAndThreadId.peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { if readMaxId > data.maxIncomingReadId { - if let toIndex = transaction.getMessage(MessageId(peerId: threadMessageId.peerId, namespace: threadMessageId.namespace, id: readMaxId))?.index { - if let count = transaction.getThreadMessageCount(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), namespace: threadMessageId.namespace, fromIdExclusive: data.maxIncomingReadId, toIndex: toIndex) { + if let toIndex = transaction.getMessage(MessageId(peerId: peerAndThreadId.peerId, namespace: Namespaces.Message.Cloud, id: readMaxId))?.index { + if let count = transaction.getThreadMessageCount(peerId: peerAndThreadId.peerId, threadId: peerAndThreadId.threadId, namespace: Namespaces.Message.Cloud, fromIdExclusive: data.maxIncomingReadId, toIndex: toIndex) { data.incomingUnreadCount = max(0, data.incomingUnreadCount - Int32(count)) } } - if let topMessageIndex = transaction.getMessageHistoryThreadTopMessage(peerId: threadMessageId.peerId, threadId: threadId, namespaces: Set([Namespaces.Message.Cloud])) { + if let topMessageIndex = transaction.getMessageHistoryThreadTopMessage(peerId: peerAndThreadId.peerId, threadId: threadId, namespaces: Set([Namespaces.Message.Cloud])) { if readMaxId >= topMessageIndex.id.id { - let containingHole = transaction.getThreadIndexHole(peerId: threadMessageId.peerId, threadId: threadId, namespace: topMessageIndex.id.namespace, containing: topMessageIndex.id.id) + let containingHole = transaction.getThreadIndexHole(peerId: peerAndThreadId.peerId, threadId: threadId, namespace: topMessageIndex.id.namespace, containing: topMessageIndex.id.id) if let _ = containingHole[.everywhere] { } else { data.incomingUnreadCount = 0 @@ -4051,7 +4299,7 @@ func replayFinalState( data.maxIncomingReadId = max(data.maxIncomingReadId, readMaxId) if let entry = StoredMessageHistoryThreadInfo(data) { - transaction.setMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), info: entry) + transaction.setMessageHistoryThreadInfo(peerId: peerAndThreadId.peerId, threadId: peerAndThreadId.threadId, info: entry) } } } @@ -4073,20 +4321,20 @@ func replayFinalState( }) } } else { - if let currentId = updatedOutgoingThreadReadStates[threadMessageId] { + if let currentId = updatedOutgoingThreadReadStates[peerAndThreadId] { if currentId < readMaxId { - updatedOutgoingThreadReadStates[threadMessageId] = readMaxId + updatedOutgoingThreadReadStates[peerAndThreadId] = readMaxId } } else { - updatedOutgoingThreadReadStates[threadMessageId] = readMaxId + updatedOutgoingThreadReadStates[peerAndThreadId] = readMaxId } - if let channel = transaction.getPeer(threadMessageId.peerId) as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isForum) { - if var data = transaction.getMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id))?.data.get(MessageHistoryThreadData.self) { + if let channel = transaction.getPeer(peerAndThreadId.peerId) as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isForum) { + if var data = transaction.getMessageHistoryThreadInfo(peerId: peerAndThreadId.peerId, threadId: peerAndThreadId.threadId)?.data.get(MessageHistoryThreadData.self) { if readMaxId >= data.maxOutgoingReadId { data.maxOutgoingReadId = readMaxId if let entry = StoredMessageHistoryThreadInfo(data) { - transaction.setMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), info: entry) + transaction.setMessageHistoryThreadInfo(peerId: peerAndThreadId.peerId, threadId: peerAndThreadId.threadId, info: entry) } } } @@ -4746,12 +4994,12 @@ func replayFinalState( if finalState.state.resetForumTopicLists[topicId.peerId] == nil { let _ = pts if let entry = StoredMessageHistoryThreadInfo(data.data) { - transaction.setMessageHistoryThreadInfo(peerId: topicId.peerId, threadId: Int64(topicId.id), info: entry) + transaction.setMessageHistoryThreadInfo(peerId: topicId.peerId, threadId: topicId.threadId, info: entry) } else { assertionFailure() } - transaction.replaceMessageTagSummary(peerId: topicId.peerId, threadId: Int64(topicId.id), tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, customTag: nil, count: data.unreadMentionCount, maxId: data.topMessageId) - transaction.replaceMessageTagSummary(peerId: topicId.peerId, threadId: Int64(topicId.id), tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, customTag: nil, count: data.unreadReactionCount, maxId: data.topMessageId) + transaction.replaceMessageTagSummary(peerId: topicId.peerId, threadId: topicId.threadId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, customTag: nil, count: data.unreadMentionCount, maxId: data.topMessageId) + transaction.replaceMessageTagSummary(peerId: topicId.peerId, threadId: topicId.threadId, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, customTag: nil, count: data.unreadReactionCount, maxId: data.topMessageId) } case let .UpdateStory(peerId, story): var updatedPeerEntries: [StoryItemsTableEntry] = transaction.getStoryItems(peerId: peerId) diff --git a/submodules/TelegramCore/Sources/State/AccountStateManager.swift b/submodules/TelegramCore/Sources/State/AccountStateManager.swift index 14dc6a371e..c81ec2048a 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManager.swift @@ -299,8 +299,8 @@ public final class AccountStateManager { return self.authorizationListUpdatesPipe.signal() } - private let threadReadStateUpdatesPipe = ValuePipe<(incoming: [MessageId: MessageId.Id], outgoing: [MessageId: MessageId.Id])>() - var threadReadStateUpdates: Signal<(incoming: [MessageId: MessageId.Id], outgoing: [MessageId: MessageId.Id]), NoError> { + private let threadReadStateUpdatesPipe = ValuePipe<(incoming: [PeerAndBoundThreadId: MessageId.Id], outgoing: [PeerAndBoundThreadId: MessageId.Id])>() + var threadReadStateUpdates: Signal<(incoming: [PeerAndBoundThreadId: MessageId.Id], outgoing: [PeerAndBoundThreadId: MessageId.Id]), NoError> { return self.threadReadStateUpdatesPipe.signal() } @@ -1916,7 +1916,7 @@ public final class AccountStateManager { } } - var threadReadStateUpdates: Signal<(incoming: [MessageId: MessageId.Id], outgoing: [MessageId: MessageId.Id]), NoError> { + var threadReadStateUpdates: Signal<(incoming: [PeerAndBoundThreadId: MessageId.Id], outgoing: [PeerAndBoundThreadId: MessageId.Id]), NoError> { return self.impl.signalWith { impl, subscriber in return impl.threadReadStateUpdates.start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) } diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index e32b516153..a212fbad0d 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -2138,8 +2138,6 @@ public final class AccountViewTracker { var isSimpleThread = false if peerId == account.peerId { isSimpleThread = true - } else if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) { - isSimpleThread = true } if isSimpleThread { diff --git a/submodules/TelegramCore/Sources/State/FetchChatList.swift b/submodules/TelegramCore/Sources/State/FetchChatList.swift index 5501c844c6..222719bf0f 100644 --- a/submodules/TelegramCore/Sources/State/FetchChatList.swift +++ b/submodules/TelegramCore/Sources/State/FetchChatList.swift @@ -204,7 +204,7 @@ struct FetchedChatList { var pinnedItemIds: [PeerId]? var folderSummaries: [PeerGroupId: PeerGroupUnreadCountersSummary] var peerGroupIds: [PeerId: PeerGroupId] - var threadInfos: [MessageId: StoreMessageHistoryThreadData] + var threadInfos: [PeerAndBoundThreadId: StoreMessageHistoryThreadData] } func fetchChatList(accountPeerId: PeerId, postbox: Postbox, network: Network, location: FetchChatListLocation, upperBound: MessageIndex, hash: Int64, limit: Int32) -> Signal { @@ -398,7 +398,7 @@ func fetchChatList(accountPeerId: PeerId, postbox: Postbox, network: Network, lo return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: storeMessages, reactions: [], result: result) |> mapToSignal { result in if let result = result { - return resolveForumThreads(accountPeerId: accountPeerId, postbox: postbox, network: network, fetchedChatList: result) + return resolveForumThreads(accountPeerId: accountPeerId, postbox: postbox, source: .network(network), fetchedChatList: result) |> map(Optional.init) } else { return .single(result) diff --git a/submodules/TelegramCore/Sources/State/Holes.swift b/submodules/TelegramCore/Sources/State/Holes.swift index 44c5eca149..a9e5e8e27d 100644 --- a/submodules/TelegramCore/Sources/State/Holes.swift +++ b/submodules/TelegramCore/Sources/State/Holes.swift @@ -189,11 +189,12 @@ func resolveUnknownEmojiFiles(postbox: Postbox, source: FetchMessageHistoryHo } } -func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistoryHoleSource, accountPeerId: PeerId, parsedPeers: AccumulatedPeers, storeMessages: [StoreMessage], _ f: @escaping (Transaction, AccumulatedPeers, [StoreMessage]) -> T) -> Signal { +func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHistoryHoleSource, accountPeerId: PeerId, parsedPeers: AccumulatedPeers, storeMessages: [StoreMessage], resolveThreads: Bool, _ f: @escaping (Transaction, AccumulatedPeers, [StoreMessage]) -> T) -> Signal { return postbox.transaction { transaction -> Signal in var storedIds = Set() var referencedReplyIds = ReferencedReplyMessageIds() var referencedGeneralIds = Set() + var threadIds = Set() for message in storeMessages { guard case let .Id(id) = message.id else { continue @@ -206,6 +207,9 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHis referencedGeneralIds.formUnion(attribute.associatedMessageIds) } } + if let threadId = message.threadId { + threadIds.insert(PeerAndBoundThreadId(peerId: id.peerId, threadId: threadId)) + } } let allPossiblyStoredReferencedIds = referencedGeneralIds.union(referencedReplyIds.targetIdsBySourceId.keys) @@ -220,8 +224,17 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHis |> mapToSignal { _ -> Signal in return resolveAssociatedStories(postbox: postbox, source: source, accountPeerId: accountPeerId, messages: storeMessages, additionalPeers: parsedPeers, result: Void()) |> mapToSignal { _ -> Signal in - return postbox.transaction { transaction -> T in - return f(transaction, parsedPeers, []) + if resolveThreads && !threadIds.isEmpty { + return resolveForumThreads(accountPeerId: accountPeerId, postbox: postbox, source: source, ids: Array(threadIds)) + |> mapToSignal { _ -> Signal in + return postbox.transaction { transaction -> T in + return f(transaction, parsedPeers, []) + } + } + } else { + return postbox.transaction { transaction -> T in + return f(transaction, parsedPeers, []) + } } } } @@ -309,12 +322,29 @@ func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMessageHis additionalPeers = additionalPeers.union(with: AccumulatedPeers(transaction: transaction, chats: chats, users: users)) } - return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: storeMessages + additionalMessages, reactions: [], result: Void()) + let combinedMessages = storeMessages + additionalMessages + return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: combinedMessages, reactions: [], result: Void()) |> mapToSignal { _ -> Signal in return resolveAssociatedStories(postbox: postbox, source: source, accountPeerId: accountPeerId, messages: storeMessages + additionalMessages, additionalPeers: parsedPeers.union(with: additionalPeers), result: Void()) |> mapToSignal { _ -> Signal in - return postbox.transaction { transaction -> T in - return f(transaction, additionalPeers, additionalMessages) + var threadIds = Set() + for message in combinedMessages { + if case let .Id(id) = message.id, let threadId = message.threadId { + threadIds.insert(PeerAndBoundThreadId(peerId: id.peerId, threadId: threadId)) + } + } + + if resolveThreads && !threadIds.isEmpty { + return resolveForumThreads(accountPeerId: accountPeerId, postbox: postbox, source: source, ids: Array(threadIds)) + |> mapToSignal { _ -> Signal in + return postbox.transaction { transaction -> T in + return f(transaction, parsedPeers, []) + } + } + } else { + return postbox.transaction { transaction -> T in + return f(transaction, additionalPeers, additionalMessages) + } } } } @@ -916,7 +946,7 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH } } - return withResolvedAssociatedMessages(postbox: postbox, source: source, accountPeerId: accountPeerId, parsedPeers: parsedPeers, storeMessages: storeMessages, { transaction, additionalParsedPeers, additionalMessages -> FetchMessageHistoryHoleResult? in + return withResolvedAssociatedMessages(postbox: postbox, source: source, accountPeerId: accountPeerId, parsedPeers: parsedPeers, storeMessages: storeMessages, resolveThreads: true, { transaction, additionalParsedPeers, additionalMessages -> FetchMessageHistoryHoleResult? in let _ = transaction.addMessages(storeMessages, location: .Random) let _ = transaction.addMessages(additionalMessages, location: .Random) var filledRange: ClosedRange @@ -1076,15 +1106,15 @@ func fetchChatListHole(postbox: Postbox, network: Network, accountPeerId: PeerId } |> ignoreValues } - return withResolvedAssociatedMessages(postbox: postbox, source: .network(network), accountPeerId: accountPeerId, parsedPeers: fetchedChats.peers, storeMessages: fetchedChats.storeMessages, { transaction, additionalPeers, additionalMessages -> Void in + return withResolvedAssociatedMessages(postbox: postbox, source: .network(network), accountPeerId: accountPeerId, parsedPeers: fetchedChats.peers, storeMessages: fetchedChats.storeMessages, resolveThreads: false, { transaction, additionalPeers, additionalMessages -> Void in updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: fetchedChats.peers.union(with: additionalPeers)) for (threadMessageId, data) in fetchedChats.threadInfos { if let entry = StoredMessageHistoryThreadInfo(data.data) { - transaction.setMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), info: entry) + transaction.setMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: threadMessageId.threadId, info: entry) } - transaction.replaceMessageTagSummary(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, customTag: nil, count: data.unreadMentionCount, maxId: data.topMessageId) - transaction.replaceMessageTagSummary(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, customTag: nil, count: data.unreadReactionCount, maxId: data.topMessageId) + transaction.replaceMessageTagSummary(peerId: threadMessageId.peerId, threadId: threadMessageId.threadId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, customTag: nil, count: data.unreadMentionCount, maxId: data.topMessageId) + transaction.replaceMessageTagSummary(peerId: threadMessageId.peerId, threadId: threadMessageId.threadId, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, customTag: nil, count: data.unreadReactionCount, maxId: data.topMessageId) } transaction.updateCurrentPeerNotificationSettings(fetchedChats.notificationSettings) diff --git a/submodules/TelegramCore/Sources/State/ResetState.swift b/submodules/TelegramCore/Sources/State/ResetState.swift index a91214f6e4..c46e2b9f69 100644 --- a/submodules/TelegramCore/Sources/State/ResetState.swift +++ b/submodules/TelegramCore/Sources/State/ResetState.swift @@ -14,7 +14,7 @@ func _internal_resetAccountState(postbox: Postbox, network: Network, accountPeer guard let fetchedChats = fetchedChats else { return .never() } - return withResolvedAssociatedMessages(postbox: postbox, source: .network(network), accountPeerId: accountPeerId, parsedPeers: fetchedChats.peers, storeMessages: fetchedChats.storeMessages, { transaction, additionalPeers, additionalMessages -> Void in + return withResolvedAssociatedMessages(postbox: postbox, source: .network(network), accountPeerId: accountPeerId, parsedPeers: fetchedChats.peers, storeMessages: fetchedChats.storeMessages, resolveThreads: false, { transaction, additionalPeers, additionalMessages -> Void in for peerId in transaction.chatListGetAllPeerIds() { if peerId.namespace != Namespaces.Peer.SecretChat { transaction.updatePeerChatListInclusion(peerId, inclusion: .notIncluded) @@ -44,10 +44,10 @@ func _internal_resetAccountState(postbox: Postbox, network: Network, accountPeer for (threadMessageId, data) in fetchedChats.threadInfos { if let entry = StoredMessageHistoryThreadInfo(data.data) { - transaction.setMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), info: entry) + transaction.setMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: threadMessageId.threadId, info: entry) } - transaction.replaceMessageTagSummary(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, customTag: nil, count: data.unreadMentionCount, maxId: data.topMessageId) - transaction.replaceMessageTagSummary(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, customTag: nil, count: data.unreadReactionCount, maxId: data.topMessageId) + transaction.replaceMessageTagSummary(peerId: threadMessageId.peerId, threadId: threadMessageId.threadId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, customTag: nil, count: data.unreadMentionCount, maxId: data.topMessageId) + transaction.replaceMessageTagSummary(peerId: threadMessageId.peerId, threadId: threadMessageId.threadId, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, customTag: nil, count: data.unreadReactionCount, maxId: data.topMessageId) } transaction.updateCurrentPeerNotificationSettings(fetchedChats.notificationSettings) diff --git a/submodules/TelegramCore/Sources/State/SynchronizePeerReadState.swift b/submodules/TelegramCore/Sources/State/SynchronizePeerReadState.swift index bed0706667..5280df8940 100644 --- a/submodules/TelegramCore/Sources/State/SynchronizePeerReadState.swift +++ b/submodules/TelegramCore/Sources/State/SynchronizePeerReadState.swift @@ -251,7 +251,7 @@ private func pushPeerReadState(network: Network, postbox: Postbox, stateManager: } if markedUnread { pushSignal = pushSignal - |> then(network.request(Api.functions.messages.markDialogUnread(flags: 1 << 0, peer: .inputDialogPeer(peer: inputPeer))) + |> then(network.request(Api.functions.messages.markDialogUnread(flags: 1 << 0, parentPeer: nil, peer: .inputDialogPeer(peer: inputPeer))) |> `catch` { _ -> Signal in return .complete() } @@ -289,7 +289,7 @@ private func pushPeerReadState(network: Network, postbox: Postbox, stateManager: if markedUnread { pushSignal = pushSignal - |> then(network.request(Api.functions.messages.markDialogUnread(flags: 1 << 0, peer: .inputDialogPeer(peer: inputPeer))) + |> then(network.request(Api.functions.messages.markDialogUnread(flags: 1 << 0, parentPeer: nil, peer: .inputDialogPeer(peer: inputPeer))) |> `catch` { _ -> Signal in return .complete() } diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index d3084d9975..b3a7ef1dfb 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -336,7 +336,7 @@ extension Api.Update { } else { return [] } - case let .updateDraftMessage(_, peer, _, _): + case let .updateDraftMessage(_, peer, _, _, _): return [peer.peerId] case let .updateNewScheduledMessage(message): return apiMessagePeerIds(message) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift index 8ae0883f6a..a3659f143d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift @@ -385,7 +385,11 @@ public extension TelegramEngine.EngineData.Item { if let cachedPeerData = view.cachedData as? CachedUserData { return cachedPeerData.sendPaidMessageStars } else if let channel = peerViewMainPeer(view) as? TelegramChannel { - return channel.sendPaidMessageStars + if channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = view.peers[linkedMonoforumId] as? TelegramChannel { + return mainChannel.sendPaidMessageStars + } else { + return channel.sendPaidMessageStars + } } else { return nil } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift index 57056ba7c0..47f7b76dd5 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift @@ -116,11 +116,10 @@ func _internal_togglePeerUnreadMarkInteractively(postbox: Postbox, network: Netw } func _internal_markForumThreadAsReadInteractively(transaction: Transaction, network: Network, viewTracker: AccountViewTracker, peerId: PeerId, threadId: Int64) { - //TODO:release monoforums guard let peer = transaction.getPeer(peerId) else { return } - guard let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) else { + guard let channel = peer as? TelegramChannel, channel.isForumOrMonoForum else { return } guard var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) else { @@ -138,15 +137,19 @@ func _internal_markForumThreadAsReadInteractively(transaction: Transaction, netw transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: threadId, info: entry) } - if let inputPeer = apiInputPeer(channel) { - //TODO:loc - let _ = network.request(Api.functions.messages.readDiscussion(peer: inputPeer, msgId: Int32(clamping: threadId), readMaxId: messageIndex.id.id)).start() + if channel.flags.contains(.isForum) { + if let inputPeer = apiInputPeer(channel) { + let _ = network.request(Api.functions.messages.readDiscussion(peer: inputPeer, msgId: Int32(clamping: threadId), readMaxId: messageIndex.id.id)).start() + } + } else if channel.flags.contains(.isMonoforum) { + if let inputPeer = apiInputPeer(channel), let subPeer = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer) { + let _ = network.request(Api.functions.messages.readSavedHistory(parentPeer: inputPeer, peer: subPeer, maxId: messageIndex.id.id)).start() + } } } } func _internal_togglePeerUnreadMarkInteractively(transaction: Transaction, network: Network, viewTracker: AccountViewTracker, peerId: PeerId, setToValue: Bool? = nil) { - //TODO:release monoforums guard let peer = transaction.getPeer(peerId) else { return } @@ -156,26 +159,33 @@ func _internal_togglePeerUnreadMarkInteractively(transaction: Transaction, netwo displayAsRegularChat = cachedData.viewForumAsMessages.knownValue ?? false } - if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum), !displayAsRegularChat { - for item in transaction.getMessageHistoryThreadIndex(peerId: peerId, limit: 20) { - guard var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: item.threadId)?.data.get(MessageHistoryThreadData.self) else { - continue - } - guard let messageIndex = transaction.getMessageHistoryThreadTopMessage(peerId: peerId, threadId: item.threadId, namespaces: Set([Namespaces.Message.Cloud])) else { - continue - } - if data.incomingUnreadCount != 0 { - data.incomingUnreadCount = 0 - data.maxIncomingReadId = max(messageIndex.id.id, data.maxIncomingReadId) - data.maxKnownMessageId = max(data.maxKnownMessageId, messageIndex.id.id) - - if let entry = StoredMessageHistoryThreadInfo(data) { - transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: item.threadId, info: entry) + if let channel = peer as? TelegramChannel { + if channel.isForumOrMonoForum, !displayAsRegularChat { + for item in transaction.getMessageHistoryThreadIndex(peerId: peerId, limit: 20) { + guard var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: item.threadId)?.data.get(MessageHistoryThreadData.self) else { + continue } - - 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() + guard let messageIndex = transaction.getMessageHistoryThreadTopMessage(peerId: peerId, threadId: item.threadId, namespaces: Set([Namespaces.Message.Cloud])) else { + continue + } + if data.incomingUnreadCount != 0 { + data.incomingUnreadCount = 0 + data.maxIncomingReadId = max(messageIndex.id.id, data.maxIncomingReadId) + data.maxKnownMessageId = max(data.maxKnownMessageId, messageIndex.id.id) + + if let entry = StoredMessageHistoryThreadInfo(data) { + transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: item.threadId, info: entry) + } + + if channel.flags.contains(.isForum) { + if let inputPeer = apiInputPeer(channel) { + let _ = network.request(Api.functions.messages.readDiscussion(peer: inputPeer, msgId: Int32(clamping: item.threadId), readMaxId: messageIndex.id.id)).start() + } + } else if channel.flags.contains(.isMonoforum) { + if let inputPeer = apiInputPeer(channel), let subPeer = transaction.getPeer(PeerId(item.threadId)).flatMap(apiInputPeer) { + let _ = network.request(Api.functions.messages.readSavedHistory(parentPeer: inputPeer, peer: subPeer, maxId: messageIndex.id.id)).start() + } + } } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ClearCloudDrafts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ClearCloudDrafts.swift index abf99a1e49..44479bd5a6 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ClearCloudDrafts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ClearCloudDrafts.swift @@ -20,9 +20,11 @@ func _internal_clearCloudDraftsInteractively(postbox: Postbox, network: Network, for update in updates { switch update { - case let .updateDraftMessage(_, peer, topMsgId, _): + case let .updateDraftMessage(_, peer, topMsgId, savedPeerId, _): var threadId: Int64? - if let topMsgId = topMsgId { + if let savedPeerId { + threadId = savedPeerId.peerId.toInt64() + } else if let topMsgId { threadId = Int64(topMsgId) } keys.insert(Key(peerId: peer.peerId, threadId: threadId)) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/MarkAllChatsAsRead.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/MarkAllChatsAsRead.swift index 144eebbf6f..6749f90f3a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/MarkAllChatsAsRead.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/MarkAllChatsAsRead.swift @@ -6,7 +6,7 @@ import MtProtoKit func _internal_markAllChatsAsRead(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal { - return network.request(Api.functions.messages.getDialogUnreadMarks()) + return network.request(Api.functions.messages.getDialogUnreadMarks(flags: 0, parentPeer: nil)) |> map(Optional.init) |> `catch` { _ -> Signal<[Api.DialogPeer]?, NoError> in return .single(nil) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift index c183bfa364..c5343465c1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift @@ -138,7 +138,7 @@ private class ReplyThreadHistoryContextImpl { guard let strongSelf = self else { return } - if let value = outgoing[referencedMessageId] { + if let value = outgoing[PeerAndBoundThreadId(peerId: referencedMessageId.peerId, threadId: Int64(referencedMessageId.id))] { strongSelf.maxReadOutgoingMessageIdValue = MessageId(peerId: data.peerId, namespace: Namespaces.Message.Cloud, id: value) } }) @@ -338,7 +338,11 @@ private class ReplyThreadHistoryContextImpl { let account = self.account - let _ = (self.account.postbox.transaction { transaction -> (Api.InputPeer?, MessageId?, Int?) in + let _ = (self.account.postbox.transaction { transaction -> (Api.InputPeer?, Api.InputPeer?, MessageId?, Int?) in + guard let peer = transaction.getPeer(peerId) else { + return (nil, nil, nil, nil) + } + if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { if messageIndex.id.id >= data.maxIncomingReadId { if let count = transaction.getThreadMessageCount(peerId: peerId, threadId: threadId, namespace: Namespaces.Message.Cloud, fromIdExclusive: data.maxIncomingReadId, toIndex: messageIndex) { @@ -364,39 +368,44 @@ private class ReplyThreadHistoryContextImpl { } } - let referencedMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)) - if let message = transaction.getMessage(referencedMessageId) { - for attribute in message.attributes { - if let attribute = attribute as? SourceReferenceMessageAttribute { - if let sourceMessage = transaction.getMessage(attribute.messageId) { - account.viewTracker.applyMaxReadIncomingMessageIdForReplyInfo(id: attribute.messageId, maxReadIncomingMessageId: messageIndex.id) - - var updatedAttribute: ReplyThreadMessageAttribute? - for i in 0 ..< sourceMessage.attributes.count { - if let attribute = sourceMessage.attributes[i] as? ReplyThreadMessageAttribute { - if let maxReadMessageId = attribute.maxReadMessageId { - if maxReadMessageId < messageIndex.id.id { + var subPeerId: Api.InputPeer? + if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) { + subPeerId = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer) + } else { + let referencedMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)) + if let message = transaction.getMessage(referencedMessageId) { + for attribute in message.attributes { + if let attribute = attribute as? SourceReferenceMessageAttribute { + if let sourceMessage = transaction.getMessage(attribute.messageId) { + account.viewTracker.applyMaxReadIncomingMessageIdForReplyInfo(id: attribute.messageId, maxReadIncomingMessageId: messageIndex.id) + + var updatedAttribute: ReplyThreadMessageAttribute? + for i in 0 ..< sourceMessage.attributes.count { + if let attribute = sourceMessage.attributes[i] as? ReplyThreadMessageAttribute { + if let maxReadMessageId = attribute.maxReadMessageId { + if maxReadMessageId < messageIndex.id.id { + updatedAttribute = ReplyThreadMessageAttribute(count: attribute.count, latestUsers: attribute.latestUsers, commentsPeerId: attribute.commentsPeerId, maxMessageId: attribute.maxMessageId, maxReadMessageId: messageIndex.id.id) + } + } else { updatedAttribute = ReplyThreadMessageAttribute(count: attribute.count, latestUsers: attribute.latestUsers, commentsPeerId: attribute.commentsPeerId, maxMessageId: attribute.maxMessageId, maxReadMessageId: messageIndex.id.id) } - } else { - updatedAttribute = ReplyThreadMessageAttribute(count: attribute.count, latestUsers: attribute.latestUsers, commentsPeerId: attribute.commentsPeerId, maxMessageId: attribute.maxMessageId, maxReadMessageId: messageIndex.id.id) + break } - break + } + if let updatedAttribute = updatedAttribute { + transaction.updateMessage(sourceMessage.id, update: { currentMessage in + var attributes = currentMessage.attributes + loop: for j in 0 ..< attributes.count { + if let _ = attributes[j] as? ReplyThreadMessageAttribute { + attributes[j] = updatedAttribute + } + } + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) } } - if let updatedAttribute = updatedAttribute { - transaction.updateMessage(sourceMessage.id, update: { currentMessage in - var attributes = currentMessage.attributes - loop: for j in 0 ..< attributes.count { - if let _ = attributes[j] as? ReplyThreadMessageAttribute { - attributes[j] = updatedAttribute - } - } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) - }) - } + break } - break } } } @@ -405,14 +414,13 @@ private class ReplyThreadHistoryContextImpl { let readCount = transaction.getThreadMessageCount(peerId: peerId, threadId: threadId, namespace: Namespaces.Message.Cloud, fromIdExclusive: fromIdExclusive, toIndex: toIndex) let topMessageId = transaction.getMessagesWithThreadId(peerId: peerId, namespace: Namespaces.Message.Cloud, threadId: threadId, from: MessageIndex.upperBound(peerId: peerId, namespace: Namespaces.Message.Cloud), includeFrom: false, to: MessageIndex.lowerBound(peerId: peerId, namespace: Namespaces.Message.Cloud), limit: 1).first?.id - return (inputPeer, topMessageId, readCount) + return (inputPeer, subPeerId, topMessageId, readCount) } - |> deliverOnMainQueue).start(next: { [weak self] inputPeer, topMessageId, readCount in + |> deliverOnMainQueue).start(next: { [weak self] inputPeer, subPeerId, topMessageId, readCount in guard let strongSelf = self else { return } - - guard let inputPeer = inputPeer else { + guard let inputPeer else { return } @@ -448,39 +456,50 @@ private class ReplyThreadHistoryContextImpl { } } - var signal = strongSelf.account.network.request(Api.functions.messages.readDiscussion(peer: inputPeer, msgId: Int32(clamping: threadId), readMaxId: messageIndex.id.id)) - |> `catch` { _ -> Signal in - return .single(.boolFalse) - } - |> ignoreValues - if revalidate { - let validateSignal = strongSelf.account.network.request(Api.functions.messages.getDiscussionMessage(peer: inputPeer, msgId: Int32(clamping: threadId))) - |> map { result -> (MessageId?, Int) in - switch result { - case let .discussionMessage(_, _, _, readInboxMaxId, _, unreadCount, _, _): - return (readInboxMaxId.flatMap({ MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }), Int(unreadCount)) - } - } - |> `catch` { _ -> Signal<(MessageId?, Int)?, NoError> in - return .single(nil) - } - |> afterNext { result in - guard let (incomingMessageId, count) = result else { - return - } - Queue.mainQueue().async { - guard let strongSelf = self else { - return - } - strongSelf.maxReadIncomingMessageIdValue = incomingMessageId - strongSelf.unreadCountValue = count - } + if let subPeerId { + let signal = strongSelf.account.network.request(Api.functions.messages.readSavedHistory(parentPeer: inputPeer, peer: subPeerId, maxId: messageIndex.id.id)) + |> `catch` { _ -> Signal in + return .single(.boolFalse) } |> ignoreValues - signal = signal - |> then(validateSignal) + if revalidate { + } + strongSelf.readDisposable.set(signal.start()) + } else { + var signal = strongSelf.account.network.request(Api.functions.messages.readDiscussion(peer: inputPeer, msgId: Int32(clamping: threadId), readMaxId: messageIndex.id.id)) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> ignoreValues + if revalidate { + let validateSignal = strongSelf.account.network.request(Api.functions.messages.getDiscussionMessage(peer: inputPeer, msgId: Int32(clamping: threadId))) + |> map { result -> (MessageId?, Int) in + switch result { + case let .discussionMessage(_, _, _, readInboxMaxId, _, unreadCount, _, _): + return (readInboxMaxId.flatMap({ MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }), Int(unreadCount)) + } + } + |> `catch` { _ -> Signal<(MessageId?, Int)?, NoError> in + return .single(nil) + } + |> afterNext { result in + guard let (incomingMessageId, count) = result else { + return + } + Queue.mainQueue().async { + guard let strongSelf = self else { + return + } + strongSelf.maxReadIncomingMessageIdValue = incomingMessageId + strongSelf.unreadCountValue = count + } + } + |> ignoreValues + signal = signal + |> then(validateSignal) + } + strongSelf.readDisposable.set(signal.start()) } - strongSelf.readDisposable.set(signal.start()) }) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift index 2916fc69cd..c8fd1ef16e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift @@ -370,7 +370,7 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation subPeer = transaction.getPeer(PeerId(threadId)) } - return (peer: peer, additionalPeer: additionalPeer, from: fromId.flatMap(transaction.getPeer), subPeer) + return (peer: peer, additionalPeer: additionalPeer, from: fromId.flatMap(transaction.getPeer), subPeer: subPeer) } |> mapToSignal { values -> Signal<(Api.messages.Messages?, Api.messages.Messages?), NoError> in guard let values = values else { @@ -408,7 +408,7 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation } else { let lowerBound = state?.main.messages.last.flatMap({ $0.index }) let signal: Signal - if peer.id.namespace == Namespaces.Peer.CloudChannel && query.isEmpty && fromId == nil && tags == nil && minDate == nil && maxDate == nil { + if peer.id.namespace == Namespaces.Peer.CloudChannel && query.isEmpty && fromId == nil && tags == nil && minDate == nil && maxDate == nil && threadId == nil { signal = account.network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: lowerBound?.id.id ?? 0, offsetDate: 0, addOffset: 0, limit: limit, maxId: Int32.max - 1, minId: 0, hash: 0)) } else { var savedReactions: [Api.Reaction]? diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index ac63032d20..a62523895c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -1713,7 +1713,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } else if item.message.id.peerId.isRepliesOrVerificationCodes { needsShareButton = false - } else if let channel = item.content.firstMessage.peers[item.content.firstMessage.id.peerId] as? TelegramChannel, channel.isMonoForum, channel.adminRights != nil, case .peer = item.chatLocation { + } else if let channel = item.content.firstMessage.peers[item.content.firstMessage.id.peerId] as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = item.content.firstMessage.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil, case .peer = item.chatLocation { if incoming { needsShareButton = true } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift index e201f39da8..b7c110d4e6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift @@ -324,10 +324,12 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible if case .replyThread = chatLocation { displayAuthorInfo = false } else { - headerSeparableThreadId = content.firstMessage.threadId - - if let threadId = content.firstMessage.threadId, let peer = content.firstMessage.peers[EnginePeer.Id(threadId)] { - headerDisplayPeer = ChatMessageDateHeader.PeerData(peer: EnginePeer(peer)) + if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = content.firstMessage.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { + headerSeparableThreadId = content.firstMessage.threadId + + if let threadId = content.firstMessage.threadId, let peer = content.firstMessage.peers[EnginePeer.Id(threadId)] { + headerDisplayPeer = ChatMessageDateHeader.PeerData(peer: EnginePeer(peer)) + } } } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 2db2475d9d..bc9423c9f3 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -574,6 +574,7 @@ private final class PeerInfoInteraction { let editingOpenStars: () -> Void let openParticipantsSection: (PeerInfoParticipantsSection) -> Void let openRecentActions: () -> Void + let openChannelMessages: () -> Void let openStats: (ChannelStatsSection) -> Void let editingOpenPreHistorySetup: () -> Void let editingOpenAutoremoveMesages: () -> Void @@ -647,6 +648,7 @@ private final class PeerInfoInteraction { editingOpenStars: @escaping () -> Void, openParticipantsSection: @escaping (PeerInfoParticipantsSection) -> Void, openRecentActions: @escaping () -> Void, + openChannelMessages: @escaping () -> Void, openStats: @escaping (ChannelStatsSection) -> Void, editingOpenPreHistorySetup: @escaping () -> Void, editingOpenAutoremoveMesages: @escaping () -> Void, @@ -719,6 +721,7 @@ private final class PeerInfoInteraction { self.editingOpenStars = editingOpenStars self.openParticipantsSection = openParticipantsSection self.openRecentActions = openRecentActions + self.openChannelMessages = openChannelMessages self.openStats = openStats self.editingOpenPreHistorySetup = editingOpenPreHistorySetup self.editingOpenAutoremoveMesages = editingOpenAutoremoveMesages @@ -2166,8 +2169,9 @@ private func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostSt let ItemBanned = 11 let ItemRecentActions = 12 let ItemAffiliatePrograms = 13 - //let ItemPostSuggestionsSettings = 14 + let ItemPostSuggestionsSettings = 14 let ItemPeerAutoTranslate = 15 + let ItemChannelMessages = 16 let isCreator = channel.flags.contains(.isCreator) @@ -2216,9 +2220,9 @@ private func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostSt })) //TODO:localize - /*items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPostSuggestionsSettings, label: .text("Off"), additionalBadgeLabel: presentationData.strings.Settings_New, text: "Post Suggestions", icon: UIImage(bundleImageName: "Chat/Info/PostSuggestionsIcon"), action: { + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPostSuggestionsSettings, label: .text(channel.linkedMonoforumId == nil ? "Off" : "On"), additionalBadgeLabel: presentationData.strings.Settings_New, text: "Message Channel", icon: UIImage(bundleImageName: "Chat/Info/PostSuggestionsIcon"), action: { interaction.editingOpenPostSuggestionsSetup() - }))*/ + })) } if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) { @@ -2351,6 +2355,13 @@ private func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostSt items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemRecentActions, label: .none, text: presentationData.strings.Group_Info_AdminLog, icon: UIImage(bundleImageName: "Chat/Info/RecentActionsIcon"), action: { interaction.openRecentActions() })) + + if channel.linkedMonoforumId != nil { + //TODO:localize + items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemChannelMessages, label: .none, text: "Channel Messages", icon: UIImage(bundleImageName: "Chat/Info/RecentActionsIcon"), action: { + interaction.openChannelMessages() + })) + } } if channel.hasPermission(.changeInfo) { @@ -3043,6 +3054,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro openRecentActions: { [weak self] in self?.openRecentActions() }, + openChannelMessages: { [weak self] in + self?.openChannelMessages() + }, openStats: { [weak self] section in self?.openStats(section: section) }, @@ -9295,6 +9309,23 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.controller?.push(controller) } + private func openChannelMessages() { + guard let channel = self.data?.peer as? TelegramChannel, let linkedMonoforumId = channel.linkedMonoforumId else { + return + } + let _ = (self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: linkedMonoforumId) + ) + |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in + guard let self, let peer else { + return + } + if let controller = self.controller, let navigationController = controller.navigationController as? NavigationController { + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) + } + }) + } + private func editingOpenPreHistorySetup() { guard let data = self.data, let peer = data.peer else { return diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index f492488a24..e00b67bb78 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -451,7 +451,12 @@ extension ChatControllerImpl { if case .pinnedMessages = presentationInterfaceState.subject { strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false) } else if let channel = peer as? TelegramChannel, channel.isMonoForum { - strongSelf.chatTitleView?.titleContent = .custom(channel.debugDisplayTitle, nil, false) + if let linkedMonoforumId = channel.linkedMonoforumId, let mainPeer = peerView.peers[linkedMonoforumId] { + //TODO:localize + strongSelf.chatTitleView?.titleContent = .custom("\(mainPeer.debugDisplayTitle) Messages", nil, false) + } else { + strongSelf.chatTitleView?.titleContent = .custom(channel.debugDisplayTitle, nil, false) + } } else { strongSelf.chatTitleView?.titleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo) let imageOverride: AvatarNodeImageOverride? @@ -720,7 +725,11 @@ extension ChatControllerImpl { if let channel = peerView.peers[peerView.peerId] as? TelegramChannel { if channel.flags.contains(.isCreator) || channel.adminRights != nil { } else { - sendPaidMessageStars = channel.sendPaidMessageStars + if channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel { + sendPaidMessageStars = mainChannel.sendPaidMessageStars + } else { + sendPaidMessageStars = channel.sendPaidMessageStars + } } } } @@ -774,7 +783,11 @@ extension ChatControllerImpl { } } else if let cachedChannelData = peerView.cachedData as? CachedChannelData { if let channel = peer as? TelegramChannel, channel.isMonoForum { - currentSendAsPeerId = channel.linkedMonoforumId + if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { + currentSendAsPeerId = channel.linkedMonoforumId + } else { + currentSendAsPeerId = nil + } } else { currentSendAsPeerId = cachedChannelData.sendAsPeerId if let channel = peer as? TelegramChannel, case .group = channel.info { @@ -1351,7 +1364,11 @@ extension ChatControllerImpl { if let channel = peerView.peers[peerView.peerId] as? TelegramChannel { if channel.flags.contains(.isCreator) || channel.adminRights != nil { } else { - sendPaidMessageStars = channel.sendPaidMessageStars + if channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel { + sendPaidMessageStars = mainChannel.sendPaidMessageStars + } else { + sendPaidMessageStars = channel.sendPaidMessageStars + } } } } @@ -1507,7 +1524,11 @@ extension ChatControllerImpl { var currentSendAsPeerId: PeerId? if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData { if peer.isMonoForum { - currentSendAsPeerId = peer.linkedMonoforumId + if let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { + currentSendAsPeerId = peer.linkedMonoforumId + } else { + currentSendAsPeerId = nil + } } else { currentSendAsPeerId = cachedData.sendAsPeerId if case .group = peer.info { @@ -1777,6 +1798,229 @@ extension ChatControllerImpl { } func reloadCachedData() { + let initialData = self.chatDisplayNode.historyNode.initialData + |> take(1) + |> beforeNext { [weak self] combinedInitialData in + guard let strongSelf = self, let combinedInitialData = combinedInitialData else { + return + } + + if let opaqueState = (combinedInitialData.initialData?.storedInterfaceState).flatMap(_internal_decodeStoredChatInterfaceState) { + var interfaceState = ChatInterfaceState.parse(opaqueState) + + var pinnedMessageId: MessageId? + var peerIsBlocked: Bool = false + var callsAvailable: Bool = true + var callsPrivate: Bool = false + var activeGroupCallInfo: ChatActiveGroupCallInfo? + var slowmodeState: ChatSlowmodeState? + if let cachedData = combinedInitialData.cachedData as? CachedChannelData { + pinnedMessageId = cachedData.pinnedMessageId + + var canBypassRestrictions = false + if let boostsToUnrestrict = cachedData.boostsToUnrestrict, let appliedBoosts = cachedData.appliedBoosts, appliedBoosts >= boostsToUnrestrict { + canBypassRestrictions = true + } + if !canBypassRestrictions, let channel = combinedInitialData.initialData?.peer as? TelegramChannel, channel.isRestrictedBySlowmode, let timeout = cachedData.slowModeTimeout { + if let slowmodeUntilTimestamp = calculateSlowmodeActiveUntilTimestamp(account: strongSelf.context.account, untilTimestamp: cachedData.slowModeValidUntilTimestamp) { + slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .timestamp(slowmodeUntilTimestamp)) + } + } + if let activeCall = cachedData.activeCall { + activeGroupCallInfo = ChatActiveGroupCallInfo(activeCall: activeCall) + } + } else if let cachedData = combinedInitialData.cachedData as? CachedUserData { + peerIsBlocked = cachedData.isBlocked + callsAvailable = cachedData.voiceCallsAvailable + callsPrivate = cachedData.callsPrivate + pinnedMessageId = cachedData.pinnedMessageId + } else if let cachedData = combinedInitialData.cachedData as? CachedGroupData { + pinnedMessageId = cachedData.pinnedMessageId + if let activeCall = cachedData.activeCall { + activeGroupCallInfo = ChatActiveGroupCallInfo(activeCall: activeCall) + } + } else if let _ = combinedInitialData.cachedData as? CachedSecretChatData { + } + + if let channel = combinedInitialData.initialData?.peer as? TelegramChannel { + if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil { + interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) + } else if channel.hasBannedPermission(.banSendVoice) != nil { + if channel.hasBannedPermission(.banSendInstantVideos) == nil { + interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video) + } + } else if channel.hasBannedPermission(.banSendInstantVideos) != nil { + if channel.hasBannedPermission(.banSendVoice) == nil { + interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) + } + } + } else if let group = combinedInitialData.initialData?.peer as? TelegramGroup { + if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) { + interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) + } else if group.hasBannedPermission(.banSendVoice) { + if !group.hasBannedPermission(.banSendInstantVideos) { + interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video) + } + } else if group.hasBannedPermission(.banSendInstantVideos) { + if !group.hasBannedPermission(.banSendVoice) { + interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) + } + } + } + + if case let .replyThread(replyThreadMessageId) = strongSelf.chatLocation { + if let channel = combinedInitialData.initialData?.peer as? TelegramChannel, channel.isForumOrMonoForum { + pinnedMessageId = nil + } else { + pinnedMessageId = replyThreadMessageId.effectiveTopId + } + } + + var pinnedMessage: ChatPinnedMessage? + if let pinnedMessageId = pinnedMessageId { + if let cachedDataMessages = combinedInitialData.cachedDataMessages { + if let message = cachedDataMessages[pinnedMessageId] { + pinnedMessage = ChatPinnedMessage(message: message, index: 0, totalCount: 1, topMessageId: message.id) + } + } + } + + var buttonKeyboardMessage = combinedInitialData.buttonKeyboardMessage + if let buttonKeyboardMessageValue = buttonKeyboardMessage, buttonKeyboardMessageValue.isRestricted(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with({ $0 })) { + buttonKeyboardMessage = nil + } + + strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { updated in + var updated = updated + + updated = updated.updatedInterfaceState({ _ in return interfaceState }) + + updated = updated.updatedKeyboardButtonsMessage(buttonKeyboardMessage) + updated = updated.updatedPinnedMessageId(pinnedMessageId) + updated = updated.updatedPinnedMessage(pinnedMessage) + updated = updated.updatedPeerIsBlocked(peerIsBlocked) + updated = updated.updatedCallsAvailable(callsAvailable) + updated = updated.updatedCallsPrivate(callsPrivate) + updated = updated.updatedActiveGroupCallInfo(activeGroupCallInfo) + updated = updated.updatedTitlePanelContext({ context in + if pinnedMessageId != nil { + if !context.contains(where: { + switch $0 { + case .pinnedMessage: + return true + default: + return false + } + }) { + var updatedContexts = context + updatedContexts.append(.pinnedMessage) + return updatedContexts.sorted() + } else { + return context + } + } else { + if let index = context.firstIndex(where: { + switch $0 { + case .pinnedMessage: + return true + default: + return false + } + }) { + var updatedContexts = context + updatedContexts.remove(at: index) + return updatedContexts + } else { + return context + } + } + }) + if let editMessage = interfaceState.editMessage, let message = combinedInitialData.initialData?.associatedMessages[editMessage.messageId] { + let (updatedState, updatedPreviewQueryState) = updatedChatEditInterfaceMessageState(context: strongSelf.context, state: updated, message: message) + updated = updatedState + strongSelf.editingUrlPreviewQueryState?.1.dispose() + strongSelf.editingUrlPreviewQueryState = updatedPreviewQueryState + } + updated = updated.updatedSlowmodeState(slowmodeState) + return updated + }) + } + if let readStateData = combinedInitialData.readStateData { + if case let .peer(peerId) = strongSelf.chatLocation, let peerReadStateData = readStateData[peerId], let notificationSettings = peerReadStateData.notificationSettings { + + let inAppSettings = strongSelf.context.sharedContext.currentInAppNotificationSettings.with { $0 } + let (count, _) = renderedTotalUnreadCount(inAppSettings: inAppSettings, totalUnreadState: peerReadStateData.totalState ?? ChatListTotalUnreadState(absoluteCounters: [:], filteredCounters: [:])) + + var globalRemainingUnreadChatCount = count + if !notificationSettings.isRemovedFromTotalUnreadCount(default: false) && peerReadStateData.unreadCount > 0 { + if case .messages = inAppSettings.totalUnreadCountDisplayCategory { + globalRemainingUnreadChatCount -= peerReadStateData.unreadCount + } else { + globalRemainingUnreadChatCount -= 1 + } + } + if globalRemainingUnreadChatCount > 0 { + strongSelf.navigationItem.badge = "\(globalRemainingUnreadChatCount)" + } else { + strongSelf.navigationItem.badge = "" + } + } + } + } + + let effectiveCachedDataReady: Signal + if case .replyThread = self.chatLocation { + effectiveCachedDataReady = self.cachedDataReady.get() + } else { + effectiveCachedDataReady = self.cachedDataReady.get() + } + var measure_isFirstTime = true + let initTimestamp = self.initTimestamp + + let mapped_chatLocationInfoReady = self._chatLocationInfoReady.get() |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "chatLocationInfoReady") + let mapped_effectiveCachedDataReady = effectiveCachedDataReady |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "effectiveCachedDataReady") + let mapped_initialDataReady = initialData |> map { $0 != nil } |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "initialDataReady") + let mapped_wallpaperReady = self.wallpaperReady.get() |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "wallpaperReady") + let mapped_presentationReady = self.presentationReady.get() |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "presentationReady") + + self.ready.set(combineLatest(queue: .mainQueue(), + mapped_chatLocationInfoReady, + mapped_effectiveCachedDataReady, + mapped_initialDataReady, + mapped_wallpaperReady, + mapped_presentationReady + ) + |> map { chatLocationInfoReady, cachedDataReady, initialData, wallpaperReady, presentationReady in + return chatLocationInfoReady && cachedDataReady && initialData && wallpaperReady && presentationReady + } + |> distinctUntilChanged + |> beforeNext { value in + if measure_isFirstTime { + measure_isFirstTime = false + #if DEBUG + let deltaTime = (CFAbsoluteTimeGetCurrent() - initTimestamp) * 1000.0 + print("Chat controller init to ready: \(deltaTime) ms") + #endif + } + }) + + self.buttonKeyboardMessageDisposable?.dispose() + self.buttonKeyboardMessageDisposable = self.chatDisplayNode.historyNode.buttonKeyboardMessage.startStrict(next: { [weak self] message in + if let strongSelf = self { + var buttonKeyboardMessageUpdated = false + if let currentButtonKeyboardMessage = strongSelf.presentationInterfaceState.keyboardButtonsMessage, let message = message { + if currentButtonKeyboardMessage.id != message.id || currentButtonKeyboardMessage.stableVersion != message.stableVersion { + buttonKeyboardMessageUpdated = true + } + } else if (strongSelf.presentationInterfaceState.keyboardButtonsMessage != nil) != (message != nil) { + buttonKeyboardMessageUpdated = true + } + if buttonKeyboardMessageUpdated { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedKeyboardButtonsMessage(message) }) + } + } + }) + if let peerId = self.chatLocation.peerId { let customEmojiAvailable: Signal = self.context.engine.data.subscribe( TelegramEngine.EngineData.Item.Peer.SecretChatLayer(id: peerId) @@ -2226,65 +2470,6 @@ extension ChatControllerImpl { self.chatDisplayNode.historyNode.voicePlaylistItemChanged(nil, currentItem) } - self.chatDisplayNode.historyNode.beganDragging = { [weak self] in - guard let self else { - return - } - if self.presentationInterfaceState.search != nil && self.presentationInterfaceState.historyFilter != nil { - self.chatDisplayNode.historyNode.addAfterTransactionsCompleted { [weak self] in - guard let self else { - return - } - - self.chatDisplayNode.dismissInput() - } - } - } - - self.chatDisplayNode.historyNode.didScrollWithOffset = { [weak self] offset, transition, itemNode, isTracking in - guard let strongSelf = self else { - return - } - - //print("didScrollWithOffset offset: \(offset), itemNode: \(String(describing: itemNode))") - - if offset > 0.0 { - if var scrolledToMessageIdValue = strongSelf.scrolledToMessageIdValue { - scrolledToMessageIdValue.allowedReplacementDirection.insert(.up) - strongSelf.scrolledToMessageIdValue = scrolledToMessageIdValue - } - } else if offset < 0.0 { - strongSelf.scrolledToMessageIdValue = nil - } - - if let currentPinchSourceItemNode = strongSelf.currentPinchSourceItemNode { - if let itemNode = itemNode { - if itemNode === currentPinchSourceItemNode { - strongSelf.currentPinchController?.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition) - } - } else { - strongSelf.currentPinchController?.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition) - } - } - - if isTracking { - strongSelf.chatDisplayNode.loadingPlaceholderNode?.addContentOffset(offset: offset, transition: transition) - } - strongSelf.chatDisplayNode.messageTransitionNode.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode, isRotated: strongSelf.chatDisplayNode.historyNode.rotated) - - } - - self.chatDisplayNode.historyNode.hasAtLeast3MessagesUpdated = { [weak self] hasAtLeast3Messages in - if let strongSelf = self { - strongSelf.updateChatPresentationInterfaceState(interactive: false, { $0.updatedHasAtLeast3Messages(hasAtLeast3Messages) }) - } - } - self.chatDisplayNode.historyNode.hasPlentyOfMessagesUpdated = { [weak self] hasPlentyOfMessages in - if let strongSelf = self { - strongSelf.updateChatPresentationInterfaceState(interactive: false, { $0.updatedHasPlentyOfMessages(hasPlentyOfMessages) }) - } - } - if case .peer(self.context.account.peerId) = self.chatLocation { var didDisplayTooltip = false if "".isEmpty { @@ -2440,192 +2625,6 @@ extension ChatControllerImpl { }) } - let initialData = self.chatDisplayNode.historyNode.initialData - |> take(1) - |> beforeNext { [weak self] combinedInitialData in - guard let strongSelf = self, let combinedInitialData = combinedInitialData else { - return - } - - if let opaqueState = (combinedInitialData.initialData?.storedInterfaceState).flatMap(_internal_decodeStoredChatInterfaceState) { - var interfaceState = ChatInterfaceState.parse(opaqueState) - - var pinnedMessageId: MessageId? - var peerIsBlocked: Bool = false - var callsAvailable: Bool = true - var callsPrivate: Bool = false - var activeGroupCallInfo: ChatActiveGroupCallInfo? - var slowmodeState: ChatSlowmodeState? - if let cachedData = combinedInitialData.cachedData as? CachedChannelData { - pinnedMessageId = cachedData.pinnedMessageId - - var canBypassRestrictions = false - if let boostsToUnrestrict = cachedData.boostsToUnrestrict, let appliedBoosts = cachedData.appliedBoosts, appliedBoosts >= boostsToUnrestrict { - canBypassRestrictions = true - } - if !canBypassRestrictions, let channel = combinedInitialData.initialData?.peer as? TelegramChannel, channel.isRestrictedBySlowmode, let timeout = cachedData.slowModeTimeout { - if let slowmodeUntilTimestamp = calculateSlowmodeActiveUntilTimestamp(account: strongSelf.context.account, untilTimestamp: cachedData.slowModeValidUntilTimestamp) { - slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .timestamp(slowmodeUntilTimestamp)) - } - } - if let activeCall = cachedData.activeCall { - activeGroupCallInfo = ChatActiveGroupCallInfo(activeCall: activeCall) - } - } else if let cachedData = combinedInitialData.cachedData as? CachedUserData { - peerIsBlocked = cachedData.isBlocked - callsAvailable = cachedData.voiceCallsAvailable - callsPrivate = cachedData.callsPrivate - pinnedMessageId = cachedData.pinnedMessageId - } else if let cachedData = combinedInitialData.cachedData as? CachedGroupData { - pinnedMessageId = cachedData.pinnedMessageId - if let activeCall = cachedData.activeCall { - activeGroupCallInfo = ChatActiveGroupCallInfo(activeCall: activeCall) - } - } else if let _ = combinedInitialData.cachedData as? CachedSecretChatData { - } - - if let channel = combinedInitialData.initialData?.peer as? TelegramChannel { - if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil { - interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) - } else if channel.hasBannedPermission(.banSendVoice) != nil { - if channel.hasBannedPermission(.banSendInstantVideos) == nil { - interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video) - } - } else if channel.hasBannedPermission(.banSendInstantVideos) != nil { - if channel.hasBannedPermission(.banSendVoice) == nil { - interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) - } - } - } else if let group = combinedInitialData.initialData?.peer as? TelegramGroup { - if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) { - interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) - } else if group.hasBannedPermission(.banSendVoice) { - if !group.hasBannedPermission(.banSendInstantVideos) { - interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video) - } - } else if group.hasBannedPermission(.banSendInstantVideos) { - if !group.hasBannedPermission(.banSendVoice) { - interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio) - } - } - } - - if case let .replyThread(replyThreadMessageId) = strongSelf.chatLocation { - if let channel = combinedInitialData.initialData?.peer as? TelegramChannel, channel.isForumOrMonoForum { - pinnedMessageId = nil - } else { - pinnedMessageId = replyThreadMessageId.effectiveTopId - } - } - - var pinnedMessage: ChatPinnedMessage? - if let pinnedMessageId = pinnedMessageId { - if let cachedDataMessages = combinedInitialData.cachedDataMessages { - if let message = cachedDataMessages[pinnedMessageId] { - pinnedMessage = ChatPinnedMessage(message: message, index: 0, totalCount: 1, topMessageId: message.id) - } - } - } - - var buttonKeyboardMessage = combinedInitialData.buttonKeyboardMessage - if let buttonKeyboardMessageValue = buttonKeyboardMessage, buttonKeyboardMessageValue.isRestricted(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with({ $0 })) { - buttonKeyboardMessage = nil - } - - strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { updated in - var updated = updated - - updated = updated.updatedInterfaceState({ _ in return interfaceState }) - - updated = updated.updatedKeyboardButtonsMessage(buttonKeyboardMessage) - updated = updated.updatedPinnedMessageId(pinnedMessageId) - updated = updated.updatedPinnedMessage(pinnedMessage) - updated = updated.updatedPeerIsBlocked(peerIsBlocked) - updated = updated.updatedCallsAvailable(callsAvailable) - updated = updated.updatedCallsPrivate(callsPrivate) - updated = updated.updatedActiveGroupCallInfo(activeGroupCallInfo) - updated = updated.updatedTitlePanelContext({ context in - if pinnedMessageId != nil { - if !context.contains(where: { - switch $0 { - case .pinnedMessage: - return true - default: - return false - } - }) { - var updatedContexts = context - updatedContexts.append(.pinnedMessage) - return updatedContexts.sorted() - } else { - return context - } - } else { - if let index = context.firstIndex(where: { - switch $0 { - case .pinnedMessage: - return true - default: - return false - } - }) { - var updatedContexts = context - updatedContexts.remove(at: index) - return updatedContexts - } else { - return context - } - } - }) - if let editMessage = interfaceState.editMessage, let message = combinedInitialData.initialData?.associatedMessages[editMessage.messageId] { - let (updatedState, updatedPreviewQueryState) = updatedChatEditInterfaceMessageState(context: strongSelf.context, state: updated, message: message) - updated = updatedState - strongSelf.editingUrlPreviewQueryState?.1.dispose() - strongSelf.editingUrlPreviewQueryState = updatedPreviewQueryState - } - updated = updated.updatedSlowmodeState(slowmodeState) - return updated - }) - } - if let readStateData = combinedInitialData.readStateData { - if case let .peer(peerId) = strongSelf.chatLocation, let peerReadStateData = readStateData[peerId], let notificationSettings = peerReadStateData.notificationSettings { - - let inAppSettings = strongSelf.context.sharedContext.currentInAppNotificationSettings.with { $0 } - let (count, _) = renderedTotalUnreadCount(inAppSettings: inAppSettings, totalUnreadState: peerReadStateData.totalState ?? ChatListTotalUnreadState(absoluteCounters: [:], filteredCounters: [:])) - - var globalRemainingUnreadChatCount = count - if !notificationSettings.isRemovedFromTotalUnreadCount(default: false) && peerReadStateData.unreadCount > 0 { - if case .messages = inAppSettings.totalUnreadCountDisplayCategory { - globalRemainingUnreadChatCount -= peerReadStateData.unreadCount - } else { - globalRemainingUnreadChatCount -= 1 - } - } - if globalRemainingUnreadChatCount > 0 { - strongSelf.navigationItem.badge = "\(globalRemainingUnreadChatCount)" - } else { - strongSelf.navigationItem.badge = "" - } - } - } - } - - self.buttonKeyboardMessageDisposable = self.chatDisplayNode.historyNode.buttonKeyboardMessage.startStrict(next: { [weak self] message in - if let strongSelf = self { - var buttonKeyboardMessageUpdated = false - if let currentButtonKeyboardMessage = strongSelf.presentationInterfaceState.keyboardButtonsMessage, let message = message { - if currentButtonKeyboardMessage.id != message.id || currentButtonKeyboardMessage.stableVersion != message.stableVersion { - buttonKeyboardMessageUpdated = true - } - } else if (strongSelf.presentationInterfaceState.keyboardButtonsMessage != nil) != (message != nil) { - buttonKeyboardMessageUpdated = true - } - if buttonKeyboardMessageUpdated { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedKeyboardButtonsMessage(message) }) - } - } - }) - if let peerId = self.chatLocation.peerId { self.chatThemeEmoticonPromise.set(self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ThemeEmoticon(id: peerId))) let chatWallpaper = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Wallpaper(id: peerId)) @@ -2653,45 +2652,6 @@ extension ChatControllerImpl { } }) - let effectiveCachedDataReady: Signal - if case .replyThread = self.chatLocation { - effectiveCachedDataReady = self.cachedDataReady.get() - } else { - effectiveCachedDataReady = self.cachedDataReady.get() - } - var measure_isFirstTime = true - let initTimestamp = self.initTimestamp - - let mapped_chatLocationInfoReady = self._chatLocationInfoReady.get() |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "chatLocationInfoReady") - let mapped_effectiveCachedDataReady = effectiveCachedDataReady |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "effectiveCachedDataReady") - let mapped_initialDataReady = initialData |> map { $0 != nil } |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "initialDataReady") - let mapped_wallpaperReady = self.wallpaperReady.get() |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "wallpaperReady") - let mapped_presentationReady = self.presentationReady.get() |> filter { $0 } |> debug_measureTimeToFirstEvent(label: "presentationReady") - - self.ready.set(combineLatest(queue: .mainQueue(), - mapped_chatLocationInfoReady, - mapped_effectiveCachedDataReady, - mapped_initialDataReady, - mapped_wallpaperReady, - mapped_presentationReady - ) - |> map { chatLocationInfoReady, cachedDataReady, initialData, wallpaperReady, presentationReady in - return chatLocationInfoReady && cachedDataReady && initialData && wallpaperReady && presentationReady - } - |> distinctUntilChanged - |> beforeNext { value in - if measure_isFirstTime { - measure_isFirstTime = false - #if DEBUG - let deltaTime = (CFAbsoluteTimeGetCurrent() - initTimestamp) * 1000.0 - print("Chat controller init to ready: \(deltaTime) ms") - #endif - } - }) - #if DEBUG - //self.ready.set(.single(true)) - #endif - if self.context.sharedContext.immediateExperimentalUISettings.crashOnLongQueries { let _ = (self.ready.get() |> filter({ $0 }) @@ -2701,129 +2661,7 @@ extension ChatControllerImpl { })).startStandalone() } - self.chatDisplayNode.historyNode.contentPositionChanged = { [weak self] offset in - guard let strongSelf = self else { return } - - var minOffsetForNavigation: CGFloat = 40.0 - strongSelf.chatDisplayNode.historyNode.enumerateItemNodes { itemNode in - if let itemNode = itemNode as? ChatMessageBubbleItemNode { - if let message = itemNode.item?.content.firstMessage, let adAttribute = message.adAttribute { - minOffsetForNavigation += itemNode.bounds.height - - switch offset { - case let .known(offset): - if offset <= 50.0 { - strongSelf.chatDisplayNode.historyNode.markAdAsSeen(opaqueId: adAttribute.opaqueId) - } - default: - break - } - } - } - return false - } - - let offsetAlpha: CGFloat - let plainInputSeparatorAlpha: CGFloat - switch offset { - case let .known(offset): - if offset < minOffsetForNavigation { - offsetAlpha = 0.0 - } else { - offsetAlpha = 1.0 - } - if offset < 4.0 { - plainInputSeparatorAlpha = 0.0 - } else { - plainInputSeparatorAlpha = 1.0 - } - case .unknown: - offsetAlpha = 1.0 - plainInputSeparatorAlpha = 1.0 - case .none: - offsetAlpha = 0.0 - plainInputSeparatorAlpha = 0.0 - } - - strongSelf.shouldDisplayDownButton = !offsetAlpha.isZero - strongSelf.controllerInteraction?.recommendedChannelsOpenUp = !strongSelf.shouldDisplayDownButton - strongSelf.updateDownButtonVisibility() - strongSelf.chatDisplayNode.updatePlainInputSeparatorAlpha(plainInputSeparatorAlpha, transition: .animated(duration: 0.2, curve: .easeInOut)) - } - - self.chatDisplayNode.historyNode.scrolledToIndex = { [weak self] toSubject, initial in - if let strongSelf = self, case let .message(index) = toSubject.index { - if case let .message(messageSubject, _, _, _) = strongSelf.subject, initial, case let .id(messageId) = messageSubject, messageId != index.id { - if messageId.peerId == index.id.peerId { - 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 { - 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) { - if toSubject.setupReply { - Queue.mainQueue().after(0.1) { - strongSelf.interfaceInteraction?.setupReplyMessage(mappedId, { _, f in f() }) - } - } - - 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: mappedId, allowedReplacementDirection: []) - - var hasQuote = false - if let quote = toSubject.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) - } - } - - strongSelf.messageContextDisposable.set((Signal.complete() |> delay(hasQuote ? 1.5 : 0.7, queue: Queue.mainQueue())).startStrict(completed: { - if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { - if controllerInteraction.highlightedState == highlightedState { - controllerInteraction.highlightedState = nil - strongSelf.updateItemNodesHighlightedStates(animated: true) - } - } - })) - - if let (messageId, params) = strongSelf.scheduledScrollToMessageId { - strongSelf.scheduledScrollToMessageId = nil - if let timecode = params.timestamp, message.id == messageId { - Queue.mainQueue().after(0.2) { - let _ = strongSelf.controllerInteraction?.openMessage(message, OpenMessageParams(mode: .timecode(timecode))) - } - } - } else if case let .message(_, _, maybeTimecode, _) = strongSelf.subject, let timecode = maybeTimecode, initial { - Queue.mainQueue().after(0.2) { - let _ = strongSelf.controllerInteraction?.openMessage(message, OpenMessageParams(mode: .timecode(timecode))) - } - } - } - } - } - } - - self.chatDisplayNode.historyNode.scrolledToSomeIndex = { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.scrolledToMessageIdValue = nil - } - - self.chatDisplayNode.historyNode.maxVisibleMessageIndexUpdated = { [weak self] index in - if let strongSelf = self, !strongSelf.historyNavigationStack.isEmpty { - strongSelf.historyNavigationStack.filterOutIndicesLessThan(index) - } - } + self.setupChatHistoryNode() self.chatDisplayNode.requestLayout = { [weak self] transition in self?.requestLayout(transition: transition) @@ -6281,6 +6119,145 @@ extension ChatControllerImpl { return self }, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get(), unblockingPeer: self.unblockingPeer.get(), searching: self.searching.get(), loadingMessage: self.loadingMessage.get(), inlineSearch: self.performingInlineSearch.get())) + self.interfaceInteraction = interfaceInteraction + + if let search = self.focusOnSearchAfterAppearance { + self.focusOnSearchAfterAppearance = nil + self.interfaceInteraction?.beginMessageSearch(search.0, search.1) + } + + self.chatDisplayNode.interfaceInteraction = interfaceInteraction + + self.context.sharedContext.mediaManager.galleryHiddenMediaManager.addTarget(self) + self.galleryHiddenMesageAndMediaDisposable.set(self.context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().startStrict(next: { [weak self] ids in + if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { + var messageIdAndMedia: [MessageId: [Media]] = [:] + + for id in ids { + if case let .chat(accountId, messageId, media) = id, accountId == strongSelf.context.account.id { + messageIdAndMedia[messageId] = [media] + } + } + + controllerInteraction.hiddenMedia = messageIdAndMedia + + strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? ChatMessageItemView { + itemNode.updateHiddenMedia() + } + } + } + })) + + self.chatDisplayNode.dismissAsOverlay = { [weak self] in + if let strongSelf = self { + strongSelf.statusBar.statusBarStyle = .Ignore + strongSelf.chatDisplayNode.animateDismissAsOverlay(completion: { + self?.dismiss() + }) + } + } + + var lastEventTimestamp: Double = 0.0 + self.networkSpeedEventsDisposable = (self.context.account.network.networkSpeedLimitedEvents + |> deliverOnMainQueue).start(next: { [weak self] event in + guard let self else { + return + } + + switch event { + case let .download(subject): + if case let .message(messageId) = subject { + var isVisible = false + self.chatDisplayNode.historyNode.forEachVisibleItemNode { itemNode in + if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item { + for (message, _) in item.content { + if message.id == messageId { + isVisible = true + } + } + } + } + + if !isVisible { + return + } + } + case .upload: + break + } + + let timestamp = CFAbsoluteTimeGetCurrent() + if lastEventTimestamp + 10.0 < timestamp { + lastEventTimestamp = timestamp + } else { + return + } + + let title: String + let text: String + switch event { + case .download: + var speedIncreaseFactor = 10 + if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["upload_premium_speedup_download"] as? Double { + speedIncreaseFactor = Int(value) + } + title = self.presentationData.strings.Chat_SpeedLimitAlert_Download_Title + text = self.presentationData.strings.Chat_SpeedLimitAlert_Download_Text("\(speedIncreaseFactor)").string + case .upload: + var speedIncreaseFactor = 10 + if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["upload_premium_speedup_upload"] as? Double { + speedIncreaseFactor = Int(value) + } + title = self.presentationData.strings.Chat_SpeedLimitAlert_Upload_Title + text = self.presentationData.strings.Chat_SpeedLimitAlert_Upload_Text("\(speedIncreaseFactor)").string + } + let content: UndoOverlayContent = .universal(animation: "anim_speed_low", scale: 0.066, colors: [:], title: title, text: text, customUndoText: nil, timeout: 5.0) + + self.context.account.network.markNetworkSpeedLimitDisplayed() + + self.present(UndoOverlayController(presentationData: self.presentationData, content: content, elevatedLayout: false, position: .top, action: { [weak self] action in + guard let self else { + return false + } + switch action { + case .info: + let context = self.context + var replaceImpl: ((ViewController) -> Void)? + let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .fasterDownload, forceDark: false, action: { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .fasterDownload, forceDark: false, dismissed: nil) + replaceImpl?(controller) + }, dismissed: nil) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + self.push(controller) + return true + default: + break + } + return false + }), in: .current) + }) + + if case .scheduledMessages = self.subject { + self.postedScheduledMessagesEventsDisposable = (self.context.account.stateManager.sentScheduledMessageIds + |> deliverOnMainQueue).start(next: { [weak self] ids in + guard let self, let peerId = self.chatLocation.peerId else { + return + } + let filteredIds = Array(ids).filter({ $0.peerId == peerId }) + if filteredIds.isEmpty { + return + } + self.displayPostedScheduledMessagesToast(ids: filteredIds) + }) + } + + self.displayNodeDidLoad() + } + + func setupChatHistoryNode() { do { let peerId = self.chatLocation.peerId if let subject = self.subject, case .scheduledMessages = subject { @@ -6289,6 +6266,7 @@ extension ChatControllerImpl { |> mapToThrottled { value -> Signal in return .single(value) |> then(.complete() |> delay(0.2, queue: Queue.mainQueue())) } + self.buttonUnreadCountDisposable?.dispose() self.buttonUnreadCountDisposable = (throttledUnreadCountSignal |> deliverOnMainQueue).startStrict(next: { [weak self] count in guard let strongSelf = self else { @@ -6298,6 +6276,7 @@ extension ChatControllerImpl { }) if case let .peer(peerId) = self.chatLocation { + self.chatUnreadCountDisposable?.dispose() self.chatUnreadCountDisposable = (self.context.engine.data.subscribe( TelegramEngine.EngineData.Item.Messages.PeerUnreadCount(id: peerId), TelegramEngine.EngineData.Item.Messages.TotalReadCounters(), @@ -6328,6 +6307,7 @@ extension ChatControllerImpl { } }) + self.chatUnreadMentionCountDisposable?.dispose() self.chatUnreadMentionCountDisposable = (self.context.account.viewTracker.unseenPersonalMessagesAndReactionCount(peerId: peerId, threadId: nil) |> deliverOnMainQueue).startStrict(next: { [weak self] mentionCount, reactionCount in if let strongSelf = self { if case .standard(.previewing) = strongSelf.presentationInterfaceState.mode { @@ -6340,6 +6320,7 @@ extension ChatControllerImpl { } }) } else if let peerId = self.chatLocation.peerId, let threadId = self.chatLocation.threadId { + self.chatUnreadMentionCountDisposable?.dispose() self.chatUnreadMentionCountDisposable = (self.context.account.viewTracker.unseenPersonalMessagesAndReactionCount(peerId: peerId, threadId: threadId) |> deliverOnMainQueue).startStrict(next: { [weak self] mentionCount, reactionCount in if let strongSelf = self { if case .standard(.previewing) = strongSelf.presentationInterfaceState.mode { @@ -6367,6 +6348,7 @@ extension ChatControllerImpl { } if let activitySpace = activitySpace, let peerId = peerId { + self.peerInputActivitiesDisposable?.dispose() self.peerInputActivitiesDisposable = (self.context.account.peerInputActivities(peerId: activitySpace) |> mapToSignal { activities -> Signal<[(Peer, PeerInputActivity)], NoError> in var foundAllPeers = true @@ -6550,6 +6532,7 @@ extension ChatControllerImpl { } })) + self.sentPeerMediaMessageEventsDisposable.dispose() self.sentPeerMediaMessageEventsDisposable.set( (self.context.account.pendingPeerMediaUploadManager.sentMessageEvents(peerId: peerId) |> deliverOnMainQueue).startStrict(next: { [weak self] _ in @@ -6561,45 +6544,131 @@ extension ChatControllerImpl { } } - self.interfaceInteraction = interfaceInteraction - - if let search = self.focusOnSearchAfterAppearance { - self.focusOnSearchAfterAppearance = nil - self.interfaceInteraction?.beginMessageSearch(search.0, search.1) - } - - self.chatDisplayNode.interfaceInteraction = interfaceInteraction - - self.context.sharedContext.mediaManager.galleryHiddenMediaManager.addTarget(self) - self.galleryHiddenMesageAndMediaDisposable.set(self.context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().startStrict(next: { [weak self] ids in - if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { - var messageIdAndMedia: [MessageId: [Media]] = [:] - - for id in ids { - if case let .chat(accountId, messageId, media) = id, accountId == strongSelf.context.account.id { - messageIdAndMedia[messageId] = [media] + self.chatDisplayNode.historyNode.contentPositionChanged = { [weak self] offset in + guard let strongSelf = self else { return } + + var minOffsetForNavigation: CGFloat = 40.0 + strongSelf.chatDisplayNode.historyNode.enumerateItemNodes { itemNode in + if let itemNode = itemNode as? ChatMessageBubbleItemNode { + if let message = itemNode.item?.content.firstMessage, let adAttribute = message.adAttribute { + minOffsetForNavigation += itemNode.bounds.height + + switch offset { + case let .known(offset): + if offset <= 50.0 { + strongSelf.chatDisplayNode.historyNode.markAdAsSeen(opaqueId: adAttribute.opaqueId) + } + default: + break + } } } - - controllerInteraction.hiddenMedia = messageIdAndMedia + return false + } - strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMessageItemView { - itemNode.updateHiddenMedia() + let offsetAlpha: CGFloat + let plainInputSeparatorAlpha: CGFloat + switch offset { + case let .known(offset): + if offset < minOffsetForNavigation { + offsetAlpha = 0.0 + } else { + offsetAlpha = 1.0 + } + if offset < 4.0 { + plainInputSeparatorAlpha = 0.0 + } else { + plainInputSeparatorAlpha = 1.0 + } + case .unknown: + offsetAlpha = 1.0 + plainInputSeparatorAlpha = 1.0 + case .none: + offsetAlpha = 0.0 + plainInputSeparatorAlpha = 0.0 + } + + strongSelf.shouldDisplayDownButton = !offsetAlpha.isZero + strongSelf.controllerInteraction?.recommendedChannelsOpenUp = !strongSelf.shouldDisplayDownButton + strongSelf.updateDownButtonVisibility() + strongSelf.chatDisplayNode.updatePlainInputSeparatorAlpha(plainInputSeparatorAlpha, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + + self.chatDisplayNode.historyNode.scrolledToIndex = { [weak self] toSubject, initial in + if let strongSelf = self, case let .message(index) = toSubject.index { + if case let .message(messageSubject, _, _, _) = strongSelf.subject, initial, case let .id(messageId) = messageSubject, messageId != index.id { + if messageId.peerId == index.id.peerId { + 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 { + 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) { + if toSubject.setupReply { + Queue.mainQueue().after(0.1) { + strongSelf.interfaceInteraction?.setupReplyMessage(mappedId, { _, f in f() }) + } + } + + 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: mappedId, allowedReplacementDirection: []) + + var hasQuote = false + if let quote = toSubject.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) + } + } + + strongSelf.messageContextDisposable.set((Signal.complete() |> delay(hasQuote ? 1.5 : 0.7, queue: Queue.mainQueue())).startStrict(completed: { + if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { + if controllerInteraction.highlightedState == highlightedState { + controllerInteraction.highlightedState = nil + strongSelf.updateItemNodesHighlightedStates(animated: true) + } + } + })) + + if let (messageId, params) = strongSelf.scheduledScrollToMessageId { + strongSelf.scheduledScrollToMessageId = nil + if let timecode = params.timestamp, message.id == messageId { + Queue.mainQueue().after(0.2) { + let _ = strongSelf.controllerInteraction?.openMessage(message, OpenMessageParams(mode: .timecode(timecode))) + } + } + } else if case let .message(_, _, maybeTimecode, _) = strongSelf.subject, let timecode = maybeTimecode, initial { + Queue.mainQueue().after(0.2) { + let _ = strongSelf.controllerInteraction?.openMessage(message, OpenMessageParams(mode: .timecode(timecode))) + } + } } } } - })) + } - self.chatDisplayNode.dismissAsOverlay = { [weak self] in - if let strongSelf = self { - strongSelf.statusBar.statusBarStyle = .Ignore - strongSelf.chatDisplayNode.animateDismissAsOverlay(completion: { - self?.dismiss() - }) + self.chatDisplayNode.historyNode.scrolledToSomeIndex = { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.scrolledToMessageIdValue = nil + } + + self.chatDisplayNode.historyNode.maxVisibleMessageIndexUpdated = { [weak self] index in + if let strongSelf = self, !strongSelf.historyNavigationStack.isEmpty { + strongSelf.historyNavigationStack.filterOutIndicesLessThan(index) } } + self.hasActiveGroupCallDisposable?.dispose() let hasActiveCalls: Signal if let callManager = self.context.sharedContext.callManager as? PresentationCallManagerImpl { hasActiveCalls = callManager.hasActiveCalls @@ -6659,6 +6728,8 @@ extension ChatControllerImpl { } } } + + self.volumeButtonsListener = nil self.volumeButtonsListener = VolumeButtonsListener( sharedContext: self.context.sharedContext, isCameraSpecific: false, @@ -6722,102 +6793,68 @@ extension ChatControllerImpl { } } - var lastEventTimestamp: Double = 0.0 - self.networkSpeedEventsDisposable = (self.context.account.network.networkSpeedLimitedEvents - |> deliverOnMainQueue).start(next: { [weak self] event in + self.chatDisplayNode.historyNode.beganDragging = { [weak self] in guard let self else { return } - - switch event { - case let .download(subject): - if case let .message(messageId) = subject { - var isVisible = false - self.chatDisplayNode.historyNode.forEachVisibleItemNode { itemNode in - if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item { - for (message, _) in item.content { - if message.id == messageId { - isVisible = true - } - } - } - } - - if !isVisible { + if self.presentationInterfaceState.search != nil && self.presentationInterfaceState.historyFilter != nil { + self.chatDisplayNode.historyNode.addAfterTransactionsCompleted { [weak self] in + guard let self else { return } + + self.chatDisplayNode.dismissInput() } - case .upload: - break } - - let timestamp = CFAbsoluteTimeGetCurrent() - if lastEventTimestamp + 10.0 < timestamp { - lastEventTimestamp = timestamp - } else { + } + + self.chatDisplayNode.historyNode.didScrollWithOffset = { [weak self] offset, transition, itemNode, isTracking in + guard let strongSelf = self else { return } + + //print("didScrollWithOffset offset: \(offset), itemNode: \(String(describing: itemNode))") - let title: String - let text: String - switch event { - case .download: - var speedIncreaseFactor = 10 - if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["upload_premium_speedup_download"] as? Double { - speedIncreaseFactor = Int(value) + if offset > 0.0 { + if var scrolledToMessageIdValue = strongSelf.scrolledToMessageIdValue { + scrolledToMessageIdValue.allowedReplacementDirection.insert(.up) + strongSelf.scrolledToMessageIdValue = scrolledToMessageIdValue } - title = self.presentationData.strings.Chat_SpeedLimitAlert_Download_Title - text = self.presentationData.strings.Chat_SpeedLimitAlert_Download_Text("\(speedIncreaseFactor)").string - case .upload: - var speedIncreaseFactor = 10 - if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["upload_premium_speedup_upload"] as? Double { - speedIncreaseFactor = Int(value) - } - title = self.presentationData.strings.Chat_SpeedLimitAlert_Upload_Title - text = self.presentationData.strings.Chat_SpeedLimitAlert_Upload_Text("\(speedIncreaseFactor)").string + } else if offset < 0.0 { + strongSelf.scrolledToMessageIdValue = nil } - let content: UndoOverlayContent = .universal(animation: "anim_speed_low", scale: 0.066, colors: [:], title: title, text: text, customUndoText: nil, timeout: 5.0) - - self.context.account.network.markNetworkSpeedLimitDisplayed() - - self.present(UndoOverlayController(presentationData: self.presentationData, content: content, elevatedLayout: false, position: .top, action: { [weak self] action in - guard let self else { - return false - } - switch action { - case .info: - let context = self.context - var replaceImpl: ((ViewController) -> Void)? - let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .fasterDownload, forceDark: false, action: { - let controller = context.sharedContext.makePremiumIntroController(context: context, source: .fasterDownload, forceDark: false, dismissed: nil) - replaceImpl?(controller) - }, dismissed: nil) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) + + if let currentPinchSourceItemNode = strongSelf.currentPinchSourceItemNode { + if let itemNode = itemNode { + if itemNode === currentPinchSourceItemNode { + strongSelf.currentPinchController?.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition) } - self.push(controller) - return true - default: - break + } else { + strongSelf.currentPinchController?.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition) } - return false - }), in: .current) - }) - - if case .scheduledMessages = self.subject { - self.postedScheduledMessagesEventsDisposable = (self.context.account.stateManager.sentScheduledMessageIds - |> deliverOnMainQueue).start(next: { [weak self] ids in - guard let self, let peerId = self.chatLocation.peerId else { - return - } - let filteredIds = Array(ids).filter({ $0.peerId == peerId }) - if filteredIds.isEmpty { - return - } - self.displayPostedScheduledMessagesToast(ids: filteredIds) - }) + } + + if isTracking { + strongSelf.chatDisplayNode.loadingPlaceholderNode?.addContentOffset(offset: offset, transition: transition) + } + strongSelf.chatDisplayNode.messageTransitionNode.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode, isRotated: strongSelf.chatDisplayNode.historyNode.rotated) + } - self.displayNodeDidLoad() + self.chatDisplayNode.historyNode.hasAtLeast3MessagesUpdated = { [weak self] hasAtLeast3Messages in + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(interactive: false, { $0.updatedHasAtLeast3Messages(hasAtLeast3Messages) }) + } + } + + self.chatDisplayNode.historyNode.hasPlentyOfMessagesUpdated = { [weak self] hasPlentyOfMessages in + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(interactive: false, { $0.updatedHasPlentyOfMessages(hasPlentyOfMessages) }) + } + } + + if self.didAppear { + self.chatDisplayNode.historyNode.canReadHistory.set(self.computedCanReadHistoryPromise.get()) + } } } diff --git a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift index 29048bb5c4..de8f854626 100644 --- a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift @@ -603,6 +603,7 @@ func updateChatPresentationInterfaceStateImpl( selfController.chatLocation = selfController.presentationInterfaceState.chatLocation selfController.reloadChatLocation() selfController.reloadCachedData() + selfController.setupChatHistoryNode() } selfController.updateDownButtonVisibility() diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index af5d8d608d..7a4be77234 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -543,7 +543,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var hasEmbeddedTitleContent = false var isEmbeddedTitleContentHidden = false - let chatLocationContextHolder: Atomic + var chatLocationContextHolder: Atomic weak var attachmentController: AttachmentController? @@ -7921,7 +7921,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum { attributes.removeAll(where: { $0 is SendAsMessageAttribute }) - if channel.adminRights != nil, let sendAsPeerId = self.presentationInterfaceState.currentSendAsPeerId { + if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil, let sendAsPeerId = self.presentationInterfaceState.currentSendAsPeerId { attributes.append(SendAsMessageAttribute(peerId: sendAsPeerId)) } } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index fdcd28bf4f..c6c8870947 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -136,7 +136,7 @@ class HistoryNodeContainer: ASDisplayNode { class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { let context: AccountContext private(set) var chatLocation: ChatLocation - private let chatLocationContextHolder: Atomic + private var chatLocationContextHolder: Atomic let controllerInteraction: ChatControllerInteraction private weak var controller: ChatControllerImpl? @@ -4914,6 +4914,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } self.chatLocation = chatLocation + self.chatLocationContextHolder = Atomic(value: nil) let historyNode = ChatHistoryListNodeImpl( context: self.context, updatedPresentationData: self.controller?.updatedPresentationData ?? (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData), diff --git a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift index 9884183341..7f4455a2d8 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift @@ -102,7 +102,7 @@ func chatHistoryViewForLocation( if tag != nil { requestAroundId = true } - if case let .replyThread(message) = chatLocation, (message.peerId == context.account.peerId || message.isMonoforumPost) { + if case let .replyThread(message) = chatLocation, (message.peerId == context.account.peerId) { preFixedReadState = .peer([:]) } @@ -151,12 +151,14 @@ func chatHistoryViewForLocation( var scrollPosition: ChatHistoryViewScrollPosition? let canScrollToRead: Bool - if case let .replyThread(message) = chatLocation, !message.isForumPost { + if case let .replyThread(message) = chatLocation, !message.isForumPost, !message.isMonoforumPost { if message.peerId == context.account.peerId { canScrollToRead = false } else { canScrollToRead = true } + } else if case let .replyThread(message) = chatLocation, message.isMonoforumPost { + canScrollToRead = true } else if view.isAddedToChatList { canScrollToRead = true } else { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index f71aca16fa..df1c5e8d4a 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -344,6 +344,8 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS } if case .broadcast = channel.info { canReply = true + } else if channel.isMonoForum { + canReply = true } } else if let group = peer as? TelegramGroup { if case .Member = group.membership { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift index 1571b29684..4528e7ec16 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift @@ -230,7 +230,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState } if channel.flags.contains(.isMonoforum) { - if channel.adminRights != nil, case .peer = chatPresentationInterfaceState.chatLocation { + if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil, case .peer = chatPresentationInterfaceState.chatLocation { if chatPresentationInterfaceState.interfaceState.replyMessageSubject == nil { displayInputTextPanel = false if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift index 57b6cd888b..b2d9415d67 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift @@ -99,6 +99,16 @@ func rightNavigationButtonForChatInterfaceState(context: AccountContext, present } } + if let channel = presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, case .peer = presentationInterfaceState.chatLocation { + if case .search(false) = currentButton?.action { + return currentButton + } else { + let buttonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(presentationInterfaceState.theme), style: .plain, target: target, action: selector) + buttonItem.accessibilityLabel = strings.Conversation_Search + return ChatNavigationButton(action: .search(hasTags: false), buttonItem: buttonItem) + } + } + if let channel = presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, let moreInfoNavigationButton = moreInfoNavigationButton { if case .replyThread = presentationInterfaceState.chatLocation { } else { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift index 518b8a1994..f4231397ae 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift @@ -232,7 +232,7 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat } func titleTopicsPanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatTitleAccessoryPanelNode?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, force: Bool) -> ChatTopicListTitleAccessoryPanelNode? { - if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, channel.adminRights != nil { + if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top if case .top = topicListDisplayMode, let peerId = chatPresentationInterfaceState.chatLocation.peerId { if let currentPanel = currentPanel as? ChatTopicListTitleAccessoryPanelNode { @@ -253,8 +253,7 @@ func sidePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState return nil } - - if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, channel.adminRights != nil { + if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top if case .side = topicListDisplayMode { return AnyComponentWithIdentity( diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index 20419a65a7..db193b7840 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -108,9 +108,9 @@ private final class OverlayTransitionContainerController: ViewController, Standa public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTransitionNode, ChatMessageTransitionProtocol { static let animationDuration: Double = 0.3 - static let verticalAnimationControlPoints: (Float, Float, Float, Float) = (0.19919472913616398, 0.010644531250000006, 0.27920937042459737, 0.91025390625) - static let verticalAnimationCurve: ContainedViewLayoutTransitionCurve = .custom(verticalAnimationControlPoints.0, verticalAnimationControlPoints.1, verticalAnimationControlPoints.2, verticalAnimationControlPoints.3) - static let horizontalAnimationCurve: ContainedViewLayoutTransitionCurve = .custom(0.23, 1.0, 0.32, 1.0) + public static let verticalAnimationControlPoints: (Float, Float, Float, Float) = (0.19919472913616398, 0.010644531250000006, 0.27920937042459737, 0.91025390625) + public static let verticalAnimationCurve: ContainedViewLayoutTransitionCurve = .custom(verticalAnimationControlPoints.0, verticalAnimationControlPoints.1, verticalAnimationControlPoints.2, verticalAnimationControlPoints.3) + public static let horizontalAnimationCurve: ContainedViewLayoutTransitionCurve = .custom(0.23, 1.0, 0.32, 1.0) final class ReplyPanel { let titleNode: ASDisplayNode diff --git a/submodules/TelegramUI/Sources/ChatTopicListTitleAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ChatTopicListTitleAccessoryPanelNode.swift index bdd5503820..2d63c8dc08 100644 --- a/submodules/TelegramUI/Sources/ChatTopicListTitleAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTopicListTitleAccessoryPanelNode.swift @@ -15,6 +15,173 @@ import SwiftSignalKit import BundleIconComponent import AvatarNode +private final class CustomBadgeComponent: Component { + public let text: String + public let font: UIFont + public let background: UIColor + public let foreground: UIColor + public let insets: UIEdgeInsets + + public init( + text: String, + font: UIFont, + background: UIColor, + foreground: UIColor, + insets: UIEdgeInsets, + ) { + self.text = text + self.font = font + self.background = background + self.foreground = foreground + self.insets = insets + } + + public static func ==(lhs: CustomBadgeComponent, rhs: CustomBadgeComponent) -> Bool { + if lhs.text != rhs.text { + return false + } + if lhs.font != rhs.font { + return false + } + if lhs.background != rhs.background { + return false + } + if lhs.foreground != rhs.foreground { + return false + } + if lhs.insets != rhs.insets { + return false + } + return true + } + + private struct TextLayout { + var size: CGSize + var opticalBounds: CGRect + + init(size: CGSize, opticalBounds: CGRect) { + self.size = size + self.opticalBounds = opticalBounds + } + } + + public final class View: UIView { + private let backgroundView: UIImageView + private let textContentsView: UIImageView + + private var textLayout: TextLayout? + + private var component: CustomBadgeComponent? + + override public init(frame: CGRect) { + self.backgroundView = UIImageView() + + self.textContentsView = UIImageView() + self.textContentsView.layer.anchorPoint = CGPoint() + + super.init(frame: frame) + + self.addSubview(self.backgroundView) + self.addSubview(self.textContentsView) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: CustomBadgeComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let previousComponent = self.component + self.component = component + + if component.text != previousComponent?.text || component.font != previousComponent?.font { + let attributedText = NSAttributedString(string: component.text, attributes: [ + NSAttributedString.Key.font: component.font, + NSAttributedString.Key.foregroundColor: UIColor.white + ]) + + var boundingRect = attributedText.boundingRect(with: availableSize, options: .usesLineFragmentOrigin, context: nil) + boundingRect.size.width = ceil(boundingRect.size.width) + boundingRect.size.height = ceil(boundingRect.size.height) + + if let context = DrawingContext(size: boundingRect.size, scale: 0.0, opaque: false, clear: true) { + context.withContext { c in + UIGraphicsPushContext(c) + defer { + UIGraphicsPopContext() + } + + attributedText.draw(at: CGPoint()) + } + var minFilledLineY = Int(context.scaledSize.height) - 1 + var maxFilledLineY = 0 + var minFilledLineX = Int(context.scaledSize.width) - 1 + var maxFilledLineX = 0 + for y in 0 ..< Int(context.scaledSize.height) { + let linePtr = context.bytes.advanced(by: max(0, y) * context.bytesPerRow).assumingMemoryBound(to: UInt32.self) + + for x in 0 ..< Int(context.scaledSize.width) { + let pixelPtr = linePtr.advanced(by: x) + if pixelPtr.pointee != 0 { + minFilledLineY = min(y, minFilledLineY) + maxFilledLineY = max(y, maxFilledLineY) + minFilledLineX = min(x, minFilledLineX) + maxFilledLineX = max(x, maxFilledLineX) + } + } + } + + var opticalBounds = CGRect() + if minFilledLineX <= maxFilledLineX && minFilledLineY <= maxFilledLineY { + opticalBounds.origin.x = CGFloat(minFilledLineX) / context.scale + opticalBounds.origin.y = CGFloat(minFilledLineY) / context.scale + opticalBounds.size.width = CGFloat(maxFilledLineX - minFilledLineX) / context.scale + opticalBounds.size.height = CGFloat(maxFilledLineY - minFilledLineY) / context.scale + } + + self.textContentsView.image = context.generateImage()?.withRenderingMode(.alwaysTemplate) + self.textLayout = TextLayout(size: boundingRect.size, opticalBounds: opticalBounds) + } else { + self.textLayout = TextLayout(size: boundingRect.size, opticalBounds: CGRect(origin: CGPoint(), size: boundingRect.size)) + } + } + + let textSize = self.textLayout?.size ?? CGSize(width: 1.0, height: 1.0) + + var size = CGSize(width: textSize.width + component.insets.left + component.insets.right, height: textSize.height + component.insets.top + component.insets.bottom) + size.width = max(size.width, size.height) + + let backgroundFrame = CGRect(origin: CGPoint(), size: size) + transition.setFrame(view: self.backgroundView, frame: backgroundFrame) + + let textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) * 0.5), y: component.insets.top + UIScreenPixel), size: textSize) + /*if let textLayout = self.textLayout { + textFrame.origin.x = textLayout.opticalBounds.minX + floorToScreenPixels((backgroundFrame.width - textLayout.opticalBounds.width) * 0.5) + textFrame.origin.y = textLayout.opticalBounds.minY + floorToScreenPixels((backgroundFrame.height - textLayout.opticalBounds.height) * 0.5) + }*/ + + transition.setPosition(view: self.textContentsView, position: textFrame.origin) + self.textContentsView.bounds = CGRect(origin: CGPoint(), size: textFrame.size) + + if size.height != self.backgroundView.image?.size.height { + self.backgroundView.image = generateStretchableFilledCircleImage(diameter: size.height, color: .white)?.withRenderingMode(.alwaysTemplate) + } + + self.backgroundView.tintColor = component.background + self.textContentsView.tintColor = component.foreground + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, ChatControllerCustomNavigationPanelNode, ASScrollViewDelegate { private struct Params: Equatable { var width: CGFloat @@ -82,6 +249,7 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C private let icon = ComponentView() private var avatarNode: AvatarNode? private let title = ComponentView() + private var badge: ComponentView? init(context: AccountContext, action: @escaping (() -> Void), contextGesture: @escaping (ContextGesture, ContextExtractedContentContainingNode) -> Void) { self.context = context @@ -146,7 +314,10 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C } func update(context: AccountContext, item: Item, isSelected: Bool, theme: PresentationTheme, height: CGFloat, transition: ComponentTransition) -> CGSize { + let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.25) + let spacing: CGFloat = 3.0 + let badgeSpacing: CGFloat = 4.0 let avatarIconContent: EmojiStatusComponent.Content if case let .forum(topicId) = item.item.id, topicId != 1, let threadData = item.item.threadData { @@ -184,7 +355,37 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C containerSize: CGSize(width: 100.0, height: 100.0) ) - let contentSize: CGFloat = iconSize.width + spacing + titleSize.width + var badgeSize: CGSize? + if let readCounters = item.item.readCounters, readCounters.count > 0 { + let badge: ComponentView + var badgeTransition = transition + if let current = self.badge { + badge = current + } else { + badgeTransition = .immediate + badge = ComponentView() + self.badge = badge + } + + badgeSize = badge.update( + transition: badgeTransition, + component: AnyComponent(CustomBadgeComponent( + text: "\(readCounters.count)", + font: Font.regular(12.0), + background: theme.list.itemCheckColors.fillColor, + foreground: theme.list.itemCheckColors.foregroundColor, + insets: UIEdgeInsets(top: 1.0, left: 5.0, bottom: 2.0, right: 5.0) + )), + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0) + ) + } + + var contentSize: CGFloat = iconSize.width + spacing + titleSize.width + if let badgeSize { + contentSize += badgeSpacing + badgeSize.width + } + let size = CGSize(width: contentSize, height: height) let iconFrame = CGRect(origin: CGPoint(x: 0.0, y: 5.0 + floor((size.height - iconSize.height) * 0.5)), size: iconSize) @@ -233,6 +434,33 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C titleView.frame = titleFrame } + if let badgeSize, let badge = self.badge { + let badgeFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + badgeSpacing, y: titleFrame.minY + floorToScreenPixels((titleFrame.height - badgeSize.height) * 0.5)), size: badgeSize) + + if let badgeView = badge.view { + if badgeView.superview == nil { + badgeView.isUserInteractionEnabled = false + self.containerButton.addSubview(badgeView) + badgeView.frame = badgeFrame + badgeView.alpha = 0.0 + } + transition.setPosition(view: badgeView, position: badgeFrame.center) + transition.setBounds(view: badgeView, bounds: CGRect(origin: CGPoint(), size: badgeFrame.size)) + transition.setScale(view: badgeView, scale: 1.0) + alphaTransition.setAlpha(view: badgeView, alpha: 1.0) + } + } else if let badge = self.badge { + self.badge = nil + if let badgeView = badge.view { + let badgeFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + badgeSpacing, y: titleFrame.minX + floorToScreenPixels((titleFrame.height - badgeView.bounds.height) * 0.5)), size: badgeView.bounds.size) + transition.setPosition(view: badgeView, position: badgeFrame.center) + transition.setScale(view: badgeView, scale: 0.001) + alphaTransition.setAlpha(view: badgeView, alpha: 0.0, completion: { [weak badgeView] _ in + badgeView?.removeFromSuperview() + }) + } + } + transition.setFrame(view: self.containerButton, frame: CGRect(origin: CGPoint(), size: size)) self.extractedContainerNode.frame = CGRect(origin: CGPoint(), size: size) @@ -563,7 +791,8 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C id: .chatList(sourceId), index: .chatList(ChatListIndex(pinningIndex: item.pinnedIndex.flatMap(UInt16.init), messageIndex: mappedMessageIndex)), messages: messages, - readCounters: nil, + readCounters: EnginePeerReadCounters( + incomingReadId: 0, outgoingReadId: 0, count: Int32(item.unreadCount), markedUnread: false), isMuted: false, draft: sourceId == accountPeerId ? draft : nil, threadData: nil, diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index 20fa49182c..03cf76aa7f 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -28,7 +28,19 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam var viewForumAsMessages: Signal = .single(false) if case let .peer(peer) = params.chatLocation, case let .channel(channel) = peer, channel.flags.contains(.isMonoforum) { - viewForumAsMessages = .single(false) + if let linkedMonoforumId = channel.linkedMonoforumId { + viewForumAsMessages = params.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: linkedMonoforumId) + ) + |> map { peer -> Bool in + guard case let .channel(channel) = peer else { + return false + } + return channel.adminRights == nil + } + } else { + viewForumAsMessages = .single(false) + } } else if case let .peer(peer) = params.chatLocation, case let .channel(channel) = peer, channel.flags.contains(.isForum) { viewForumAsMessages = params.context.account.postbox.combinedView(keys: [.cachedPeerData(peerId: peer.id)]) |> take(1) @@ -54,11 +66,6 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam let _ = (viewForumAsMessages |> take(1) |> deliverOnMainQueue).start(next: { viewForumAsMessages in - var viewForumAsMessages = viewForumAsMessages - if case let .peer(peer) = params.chatLocation, case let .channel(channel) = peer, channel.flags.contains(.isMonoforum), channel.adminRights == nil { - viewForumAsMessages = true - } - if case let .peer(peer) = params.chatLocation, case let .channel(channel) = peer, channel.flags.contains(.isForum), !viewForumAsMessages { for controller in params.navigationController.viewControllers.reversed() { var chatListController: ChatListControllerImpl?