diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index 0ea081769f..19ecfba9b8 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -308,16 +308,19 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch } } - if isUnread { - items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAsRead, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsRead"), color: theme.contextMenu.primaryColor) }, action: { _, f in - let _ = context.engine.messages.togglePeersUnreadMarkInteractively(peerIds: [peerId], setToValue: nil).start() - f(.default) - }))) + if case let .channel(channel) = peer, channel.flags.contains(.isForum) { } else { - items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAsUnread, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsUnread"), color: theme.contextMenu.primaryColor) }, action: { _, f in - let _ = context.engine.messages.togglePeersUnreadMarkInteractively(peerIds: [peerId], setToValue: nil).start() - f(.default) - }))) + if isUnread { + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAsRead, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsRead"), color: theme.contextMenu.primaryColor) }, action: { _, f in + let _ = context.engine.messages.togglePeersUnreadMarkInteractively(peerIds: [peerId], setToValue: nil).start() + f(.default) + }))) + } else { + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAsUnread, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsUnread"), color: theme.contextMenu.primaryColor) }, action: { _, f in + let _ = context.engine.messages.togglePeersUnreadMarkInteractively(peerIds: [peerId], setToValue: nil).start() + f(.default) + }))) + } } let archiveEnabled = !isSavedMessages && peerId != PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(777000)) && peerId == context.account.peerId @@ -479,3 +482,69 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch } } } + +func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: Int64, isPinned: Bool, chatListController: ChatListControllerImpl?, joined: Bool) -> Signal<[ContextMenuItem], NoError> { + let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) + let strings = presentationData.strings + + return combineLatest( + context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + ), + context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.ThreadData(id: peerId, threadId: threadId) + ) + ) + |> mapToSignal { peer, threadData -> Signal<[ContextMenuItem], NoError> in + guard case let .channel(channel) = peer else { + return .single([]) + } + guard let threadData = threadData else { + return .single([]) + } + + var items: [ContextMenuItem] = [] + + var canManage: Bool = false + if channel.hasPermission(.pinMessages) { + canManage = true + } + + if canManage { + //TODO:localize + items.append(.action(ContextMenuActionItem(text: isPinned ? "Unpin" : "Pin", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin": "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in + f(.default) + + let _ = context.engine.peers.setForumChannelTopicPinned(id: peerId, threadId: threadId, isPinned: !isPinned).start() + }))) + } + + var isMuted = false + if case .muted = threadData.notificationSettings.muteState { + isMuted = true + } + items.append(.action(ContextMenuActionItem(text: isMuted ? strings.ChatList_Context_Unmute : strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { _, f in + let _ = (context.engine.peers.togglePeerMuted(peerId: peerId, threadId: threadId) + |> deliverOnMainQueue).start(completed: { + f(.default) + }) + }))) + + if canManage || threadData.isOwnedByMe { + //TODO:localize + items.append(.action(ContextMenuActionItem(text: threadData.isClosed ? "Restart" : "Close", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin": "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in + f(.default) + + let _ = context.engine.peers.setForumChannelTopicClosed(id: peerId, threadId: threadId, isClosed: !threadData.isClosed).start() + }))) + + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak chatListController] _, f in + f(.default) + + chatListController?.deletePeerThread(peerId: peerId, threadId: threadId) + }))) + } + + return .single(items) + } +} diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 6a2d8f8cd2..166d3bac2a 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1635,17 +1635,44 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: archiveContextMenuItems(context: strongSelf.context, groupId: groupId._asGroup(), chatListController: strongSelf) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) case let .peer(_, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _): - let source: ContextContentSource - if let location = location { - source = .location(ChatListContextLocationContentSource(controller: strongSelf, location: location)) - } else { - let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peer.peerId), subject: nil, botStart: nil, mode: .standard(previewing: true)) + switch item.index { + case .chatList: + if case let .channel(channel) = peer.peer, channel.flags.contains(.isForum) { + let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .forum(peerId: channel.id), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false) + chatListController.navigationPresentation = .master + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + strongSelf.presentInGlobalOverlay(contextController) + } else { + let source: ContextContentSource + if let location = location { + source = .location(ChatListContextLocationContentSource(controller: strongSelf, location: location)) + } else { + let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peer.peerId), subject: nil, botStart: nil, mode: .standard(previewing: true)) + chatController.canReadHistory.set(false) + source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) + } + + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + strongSelf.presentInGlobalOverlay(contextController) + } + case let .forum(pinnedIndex, _, threadId, _, _): + let isPinned: Bool + switch pinnedIndex { + case .index: + isPinned = true + case .none: + isPinned = false + } + let source: ContextContentSource + let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .replyThread(message: ChatReplyThreadMessage( + messageId: MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false + )), subject: nil, botStart: nil, mode: .standard(previewing: true)) chatController.canReadHistory.set(false) source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) + + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: isPinned, chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + strongSelf.presentInGlobalOverlay(contextController) } - - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) - strongSelf.presentInGlobalOverlay(contextController) } } @@ -3678,7 +3705,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }) } - private func deletePeerThread(peerId: EnginePeer.Id, threadId: Int64) { + func deletePeerThread(peerId: EnginePeer.Id, threadId: Int64) { let actionSheet = ActionSheetController(presentationData: self.presentationData) var items: [ActionSheetItem] = [] diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index edc2cb8e03..6aa39be192 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -98,10 +98,13 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-421545947] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeTitle($0) } dict[1783299128] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeUsername($0) } dict[-263212119] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeUsernames($0) } + dict[1483767080] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionCreateTopic($0) } dict[771095562] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionDefaultBannedRights($0) } dict[1121994683] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionDeleteMessage($0) } + dict[-1374254839] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionDeleteTopic($0) } dict[-610299584] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionDiscardGroupCall($0) } dict[1889215493] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionEditMessage($0) } + dict[-261103096] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionEditTopic($0) } dict[1515256996] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionExportedInviteDelete($0) } dict[-384910503] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionExportedInviteEdit($0) } dict[1091179342] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionExportedInviteRevoke($0) } @@ -115,9 +118,11 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-422036098] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantToggleBan($0) } dict[-431740480] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantUnmute($0) } dict[1048537159] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantVolume($0) } + dict[1569535291] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionPinTopic($0) } dict[663693416] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionSendMessage($0) } dict[589338437] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionStartGroupCall($0) } dict[-1895328189] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionStopPoll($0) } + dict[46949251] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionToggleForum($0) } dict[1456906823] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionToggleGroupCallSetting($0) } dict[460916654] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionToggleInvites($0) } dict[-886388890] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionToggleNoForwards($0) } @@ -360,6 +365,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[215889721] = { return Api.InputStickerSet.parse_inputStickerSetAnimatedEmojiAnimations($0) } dict[-427863538] = { return Api.InputStickerSet.parse_inputStickerSetDice($0) } dict[701560302] = { return Api.InputStickerSet.parse_inputStickerSetEmojiDefaultStatuses($0) } + dict[1153562857] = { return Api.InputStickerSet.parse_inputStickerSetEmojiDefaultTopicIcons($0) } dict[80008398] = { return Api.InputStickerSet.parse_inputStickerSetEmojiGenericAnimations($0) } dict[-4838507] = { return Api.InputStickerSet.parse_inputStickerSetEmpty($0) } dict[-1645763991] = { return Api.InputStickerSet.parse_inputStickerSetID($0) } diff --git a/submodules/TelegramApi/Sources/Api10.swift b/submodules/TelegramApi/Sources/Api10.swift index 7676164a2b..5d867ef90d 100644 --- a/submodules/TelegramApi/Sources/Api10.swift +++ b/submodules/TelegramApi/Sources/Api10.swift @@ -4,6 +4,7 @@ public extension Api { case inputStickerSetAnimatedEmojiAnimations case inputStickerSetDice(emoticon: String) case inputStickerSetEmojiDefaultStatuses + case inputStickerSetEmojiDefaultTopicIcons case inputStickerSetEmojiGenericAnimations case inputStickerSetEmpty case inputStickerSetID(id: Int64, accessHash: Int64) @@ -35,6 +36,12 @@ public extension Api { buffer.appendInt32(701560302) } + break + case .inputStickerSetEmojiDefaultTopicIcons: + if boxed { + buffer.appendInt32(1153562857) + } + break case .inputStickerSetEmojiGenericAnimations: if boxed { @@ -80,6 +87,8 @@ public extension Api { return ("inputStickerSetDice", [("emoticon", String(describing: emoticon))]) case .inputStickerSetEmojiDefaultStatuses: return ("inputStickerSetEmojiDefaultStatuses", []) + case .inputStickerSetEmojiDefaultTopicIcons: + return ("inputStickerSetEmojiDefaultTopicIcons", []) case .inputStickerSetEmojiGenericAnimations: return ("inputStickerSetEmojiGenericAnimations", []) case .inputStickerSetEmpty: @@ -113,6 +122,9 @@ public extension Api { public static func parse_inputStickerSetEmojiDefaultStatuses(_ reader: BufferReader) -> InputStickerSet? { return Api.InputStickerSet.inputStickerSetEmojiDefaultStatuses } + public static func parse_inputStickerSetEmojiDefaultTopicIcons(_ reader: BufferReader) -> InputStickerSet? { + return Api.InputStickerSet.inputStickerSetEmojiDefaultTopicIcons + } public static func parse_inputStickerSetEmojiGenericAnimations(_ reader: BufferReader) -> InputStickerSet? { return Api.InputStickerSet.inputStickerSetEmojiGenericAnimations } diff --git a/submodules/TelegramApi/Sources/Api2.swift b/submodules/TelegramApi/Sources/Api2.swift index ab36cf4abb..e44293cb8a 100644 --- a/submodules/TelegramApi/Sources/Api2.swift +++ b/submodules/TelegramApi/Sources/Api2.swift @@ -616,10 +616,13 @@ public extension Api { case channelAdminLogEventActionChangeTitle(prevValue: String, newValue: String) case channelAdminLogEventActionChangeUsername(prevValue: String, newValue: String) case channelAdminLogEventActionChangeUsernames(prevValue: [String], newValue: [String]) + case channelAdminLogEventActionCreateTopic(topic: Api.ForumTopic) case channelAdminLogEventActionDefaultBannedRights(prevBannedRights: Api.ChatBannedRights, newBannedRights: Api.ChatBannedRights) case channelAdminLogEventActionDeleteMessage(message: Api.Message) + case channelAdminLogEventActionDeleteTopic(topic: Api.ForumTopic) case channelAdminLogEventActionDiscardGroupCall(call: Api.InputGroupCall) case channelAdminLogEventActionEditMessage(prevMessage: Api.Message, newMessage: Api.Message) + case channelAdminLogEventActionEditTopic(prevTopic: Api.ForumTopic, newTopic: Api.ForumTopic) case channelAdminLogEventActionExportedInviteDelete(invite: Api.ExportedChatInvite) case channelAdminLogEventActionExportedInviteEdit(prevInvite: Api.ExportedChatInvite, newInvite: Api.ExportedChatInvite) case channelAdminLogEventActionExportedInviteRevoke(invite: Api.ExportedChatInvite) @@ -633,9 +636,11 @@ public extension Api { case channelAdminLogEventActionParticipantToggleBan(prevParticipant: Api.ChannelParticipant, newParticipant: Api.ChannelParticipant) case channelAdminLogEventActionParticipantUnmute(participant: Api.GroupCallParticipant) case channelAdminLogEventActionParticipantVolume(participant: Api.GroupCallParticipant) + case channelAdminLogEventActionPinTopic(flags: Int32, prevTopic: Api.ForumTopic?, newTopic: Api.ForumTopic?) case channelAdminLogEventActionSendMessage(message: Api.Message) case channelAdminLogEventActionStartGroupCall(call: Api.InputGroupCall) case channelAdminLogEventActionStopPoll(message: Api.Message) + case channelAdminLogEventActionToggleForum(newValue: Api.Bool) case channelAdminLogEventActionToggleGroupCallSetting(joinMuted: Api.Bool) case channelAdminLogEventActionToggleInvites(newValue: Api.Bool) case channelAdminLogEventActionToggleNoForwards(newValue: Api.Bool) @@ -724,6 +729,12 @@ public extension Api { serializeString(item, buffer: buffer, boxed: false) } break + case .channelAdminLogEventActionCreateTopic(let topic): + if boxed { + buffer.appendInt32(1483767080) + } + topic.serialize(buffer, true) + break case .channelAdminLogEventActionDefaultBannedRights(let prevBannedRights, let newBannedRights): if boxed { buffer.appendInt32(771095562) @@ -737,6 +748,12 @@ public extension Api { } message.serialize(buffer, true) break + case .channelAdminLogEventActionDeleteTopic(let topic): + if boxed { + buffer.appendInt32(-1374254839) + } + topic.serialize(buffer, true) + break case .channelAdminLogEventActionDiscardGroupCall(let call): if boxed { buffer.appendInt32(-610299584) @@ -750,6 +767,13 @@ public extension Api { prevMessage.serialize(buffer, true) newMessage.serialize(buffer, true) break + case .channelAdminLogEventActionEditTopic(let prevTopic, let newTopic): + if boxed { + buffer.appendInt32(-261103096) + } + prevTopic.serialize(buffer, true) + newTopic.serialize(buffer, true) + break case .channelAdminLogEventActionExportedInviteDelete(let invite): if boxed { buffer.appendInt32(1515256996) @@ -832,6 +856,14 @@ public extension Api { } participant.serialize(buffer, true) break + case .channelAdminLogEventActionPinTopic(let flags, let prevTopic, let newTopic): + if boxed { + buffer.appendInt32(1569535291) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {prevTopic!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {newTopic!.serialize(buffer, true)} + break case .channelAdminLogEventActionSendMessage(let message): if boxed { buffer.appendInt32(663693416) @@ -850,6 +882,12 @@ public extension Api { } message.serialize(buffer, true) break + case .channelAdminLogEventActionToggleForum(let newValue): + if boxed { + buffer.appendInt32(46949251) + } + newValue.serialize(buffer, true) + break case .channelAdminLogEventActionToggleGroupCallSetting(let joinMuted): if boxed { buffer.appendInt32(1456906823) @@ -918,14 +956,20 @@ public extension Api { return ("channelAdminLogEventActionChangeUsername", [("prevValue", String(describing: prevValue)), ("newValue", String(describing: newValue))]) case .channelAdminLogEventActionChangeUsernames(let prevValue, let newValue): return ("channelAdminLogEventActionChangeUsernames", [("prevValue", String(describing: prevValue)), ("newValue", String(describing: newValue))]) + case .channelAdminLogEventActionCreateTopic(let topic): + return ("channelAdminLogEventActionCreateTopic", [("topic", String(describing: topic))]) case .channelAdminLogEventActionDefaultBannedRights(let prevBannedRights, let newBannedRights): return ("channelAdminLogEventActionDefaultBannedRights", [("prevBannedRights", String(describing: prevBannedRights)), ("newBannedRights", String(describing: newBannedRights))]) case .channelAdminLogEventActionDeleteMessage(let message): return ("channelAdminLogEventActionDeleteMessage", [("message", String(describing: message))]) + case .channelAdminLogEventActionDeleteTopic(let topic): + return ("channelAdminLogEventActionDeleteTopic", [("topic", String(describing: topic))]) case .channelAdminLogEventActionDiscardGroupCall(let call): return ("channelAdminLogEventActionDiscardGroupCall", [("call", String(describing: call))]) case .channelAdminLogEventActionEditMessage(let prevMessage, let newMessage): return ("channelAdminLogEventActionEditMessage", [("prevMessage", String(describing: prevMessage)), ("newMessage", String(describing: newMessage))]) + case .channelAdminLogEventActionEditTopic(let prevTopic, let newTopic): + return ("channelAdminLogEventActionEditTopic", [("prevTopic", String(describing: prevTopic)), ("newTopic", String(describing: newTopic))]) case .channelAdminLogEventActionExportedInviteDelete(let invite): return ("channelAdminLogEventActionExportedInviteDelete", [("invite", String(describing: invite))]) case .channelAdminLogEventActionExportedInviteEdit(let prevInvite, let newInvite): @@ -952,12 +996,16 @@ public extension Api { return ("channelAdminLogEventActionParticipantUnmute", [("participant", String(describing: participant))]) case .channelAdminLogEventActionParticipantVolume(let participant): return ("channelAdminLogEventActionParticipantVolume", [("participant", String(describing: participant))]) + case .channelAdminLogEventActionPinTopic(let flags, let prevTopic, let newTopic): + return ("channelAdminLogEventActionPinTopic", [("flags", String(describing: flags)), ("prevTopic", String(describing: prevTopic)), ("newTopic", String(describing: newTopic))]) case .channelAdminLogEventActionSendMessage(let message): return ("channelAdminLogEventActionSendMessage", [("message", String(describing: message))]) case .channelAdminLogEventActionStartGroupCall(let call): return ("channelAdminLogEventActionStartGroupCall", [("call", String(describing: call))]) case .channelAdminLogEventActionStopPoll(let message): return ("channelAdminLogEventActionStopPoll", [("message", String(describing: message))]) + case .channelAdminLogEventActionToggleForum(let newValue): + return ("channelAdminLogEventActionToggleForum", [("newValue", String(describing: newValue))]) case .channelAdminLogEventActionToggleGroupCallSetting(let joinMuted): return ("channelAdminLogEventActionToggleGroupCallSetting", [("joinMuted", String(describing: joinMuted))]) case .channelAdminLogEventActionToggleInvites(let newValue): @@ -1135,6 +1183,19 @@ public extension Api { return nil } } + public static func parse_channelAdminLogEventActionCreateTopic(_ reader: BufferReader) -> ChannelAdminLogEventAction? { + var _1: Api.ForumTopic? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.ForumTopic + } + let _c1 = _1 != nil + if _c1 { + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionCreateTopic(topic: _1!) + } + else { + return nil + } + } public static func parse_channelAdminLogEventActionDefaultBannedRights(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.ChatBannedRights? if let signature = reader.readInt32() { @@ -1166,6 +1227,19 @@ public extension Api { return nil } } + public static func parse_channelAdminLogEventActionDeleteTopic(_ reader: BufferReader) -> ChannelAdminLogEventAction? { + var _1: Api.ForumTopic? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.ForumTopic + } + let _c1 = _1 != nil + if _c1 { + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionDeleteTopic(topic: _1!) + } + else { + return nil + } + } public static func parse_channelAdminLogEventActionDiscardGroupCall(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.InputGroupCall? if let signature = reader.readInt32() { @@ -1197,6 +1271,24 @@ public extension Api { return nil } } + public static func parse_channelAdminLogEventActionEditTopic(_ reader: BufferReader) -> ChannelAdminLogEventAction? { + var _1: Api.ForumTopic? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.ForumTopic + } + var _2: Api.ForumTopic? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.ForumTopic + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionEditTopic(prevTopic: _1!, newTopic: _2!) + } + else { + return nil + } + } public static func parse_channelAdminLogEventActionExportedInviteDelete(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.ExportedChatInvite? if let signature = reader.readInt32() { @@ -1364,6 +1456,27 @@ public extension Api { return nil } } + public static func parse_channelAdminLogEventActionPinTopic(_ reader: BufferReader) -> ChannelAdminLogEventAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.ForumTopic? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.ForumTopic + } } + var _3: Api.ForumTopic? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.ForumTopic + } } + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionPinTopic(flags: _1!, prevTopic: _2, newTopic: _3) + } + else { + return nil + } + } public static func parse_channelAdminLogEventActionSendMessage(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.Message? if let signature = reader.readInt32() { @@ -1403,6 +1516,19 @@ public extension Api { return nil } } + public static func parse_channelAdminLogEventActionToggleForum(_ reader: BufferReader) -> ChannelAdminLogEventAction? { + var _1: Api.Bool? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Bool + } + let _c1 = _1 != nil + if _c1 { + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionToggleForum(newValue: _1!) + } + else { + return nil + } + } public static func parse_channelAdminLogEventActionToggleGroupCallSetting(_ reader: BufferReader) -> ChannelAdminLogEventAction? { var _1: Api.Bool? if let signature = reader.readInt32() { diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index 8ff3a6203e..39d4a6c84f 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -1874,6 +1874,21 @@ public extension Api.functions.channels { }) } } +public extension Api.functions.channels { + static func deactivateAllUsernames(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(170155475) + channel.serialize(buffer, true) + return (FunctionDescription(name: "channels.deactivateAllUsernames", parameters: [("channel", String(describing: channel))]), 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.channels { static func deleteChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index 93f73fc34f..5e970f1973 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -1185,6 +1185,7 @@ public class Account { self.managedOperationsDisposable.add(self.managedTopReactionsDisposable) self.managedOperationsDisposable.add(_internal_loadedStickerPack(postbox: self.postbox, network: self.network, reference: .iconStatusEmoji, forceActualized: true).start()) + self.managedOperationsDisposable.add(_internal_loadedStickerPack(postbox: self.postbox, network: self.network, reference: .iconTopicEmoji, forceActualized: true).start()) } if !supplementary { diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index aad6dac5cf..1c6485cdff 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -98,7 +98,7 @@ enum AccountStateMutationOperation { case UpdateMessageForwardsCount(MessageId, Int32) case UpdateInstalledStickerPacks(AccountStateUpdateStickerPacksOperation) case UpdateRecentGifs - case UpdateChatInputState(PeerId, SynchronizeableChatInputState?) + case UpdateChatInputState(PeerId, Int64?, SynchronizeableChatInputState?) case UpdateCall(Api.PhoneCall) case AddCallSignalingData(Int64, Data) case UpdateLangPack(String, Api.LangPackDifference?) @@ -489,8 +489,8 @@ struct AccountMutableState { self.addOperation(.UpdateRecentGifs) } - mutating func addUpdateChatInputState(peerId: PeerId, state: SynchronizeableChatInputState?) { - self.addOperation(.UpdateChatInputState(peerId, state)) + mutating func addUpdateChatInputState(peerId: PeerId, threadId: Int64?, state: SynchronizeableChatInputState?) { + self.addOperation(.UpdateChatInputState(peerId, threadId, state)) } mutating func addUpdateCall(_ call: Api.PhoneCall) { diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift index 9ec4de16b0..c69d4e215d 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift @@ -68,6 +68,8 @@ extension StickerPackReference { self = .emojiGenericAnimations case .inputStickerSetEmojiDefaultStatuses: self = .iconStatusEmoji + case .inputStickerSetEmojiDefaultTopicIcons: + self = .iconTopicEmoji } } } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index a8dfb72b08..eb055a18e7 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -1410,7 +1410,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo updatedState.addUpdateInstalledStickerPacks(.sync) case .updateSavedGifs: updatedState.addUpdateRecentGifs() - case let .updateDraftMessage(_, peer, _, draft): + case let .updateDraftMessage(_, peer, topMsgId, draft): let inputState: SynchronizeableChatInputState? switch draft { case .draftMessageEmpty: @@ -1422,7 +1422,11 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo } inputState = SynchronizeableChatInputState(replyToMessageId: replyToMessageId, text: message, entities: messageTextEntitiesFromApiEntities(entities ?? []), timestamp: date, textSelection: nil) } - updatedState.addUpdateChatInputState(peerId: peer.peerId, state: inputState) + var threadId: Int64? + if let topMsgId = topMsgId { + threadId = Int64(topMsgId) + } + updatedState.addUpdateChatInputState(peerId: peer.peerId, threadId: threadId, state: inputState) case let .updatePhoneCall(phoneCall): updatedState.addUpdateCall(phoneCall) case let .updatePhoneCallSignalingData(phoneCallId, data): @@ -3737,8 +3741,8 @@ func replayFinalState( stickerPackOperations.append(operation) case .UpdateRecentGifs: syncRecentGifs = true - case let .UpdateChatInputState(peerId, inputState): - _internal_updateChatInputState(transaction: transaction, peerId: peerId, inputState: inputState) + case let .UpdateChatInputState(peerId, threadId, inputState): + _internal_updateChatInputState(transaction: transaction, peerId: peerId, threadId: threadId, inputState: inputState) case let .UpdateCall(call): updatedCalls.append(call) case let .AddCallSignalingData(callId, data): diff --git a/submodules/TelegramCore/Sources/State/ManagedSynchronizeChatInputStateOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSynchronizeChatInputStateOperations.swift index 549f85b092..47dfeebad7 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSynchronizeChatInputStateOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSynchronizeChatInputStateOperations.swift @@ -100,9 +100,9 @@ func managedSynchronizeChatInputStateOperations(postbox: Postbox, network: Netwo } return .complete() }) - |> then(postbox.transaction { transaction -> Void in - let _ = transaction.operationLogRemoveEntry(peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex) - }) + |> then(postbox.transaction { transaction -> Void in + let _ = transaction.operationLogRemoveEntry(peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex) + }) disposable.set(signal.start()) } diff --git a/submodules/TelegramCore/Sources/State/SynchronizeSavedStickersOperation.swift b/submodules/TelegramCore/Sources/State/SynchronizeSavedStickersOperation.swift index 1c0e89cdda..30dbb8d72d 100644 --- a/submodules/TelegramCore/Sources/State/SynchronizeSavedStickersOperation.swift +++ b/submodules/TelegramCore/Sources/State/SynchronizeSavedStickersOperation.swift @@ -61,7 +61,7 @@ public func addSavedSticker(postbox: Postbox, network: Network, file: TelegramMe if !found { fetchReference = packReference } - case .animatedEmoji, .animatedEmojiAnimations, .dice, .premiumGifts, .emojiGenericAnimations, .iconStatusEmoji: + case .animatedEmoji, .animatedEmojiAnimations, .dice, .premiumGifts, .emojiGenericAnimations, .iconStatusEmoji, .iconTopicEmoji: break } if let fetchReference = fetchReference { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index 97fa03c16b..c28f3cb6f6 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -50,6 +50,7 @@ public struct Namespaces { public static let CloudEmojiPacks: Int32 = 8 public static let CloudEmojiGenericAnimations: Int32 = 9 public static let CloudIconStatusEmoji: Int32 = 10 + public static let CloudIconTopicEmoji: Int32 = 11 } public struct OrderedItemList { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_SynchronizeableChatInputState.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_SynchronizeableChatInputState.swift index f291ba736a..6c9bd4eda0 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_SynchronizeableChatInputState.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_SynchronizeableChatInputState.swift @@ -83,10 +83,16 @@ class InternalChatInterfaceState: Codable { } } -func _internal_updateChatInputState(transaction: Transaction, peerId: PeerId, inputState: SynchronizeableChatInputState?) { +func _internal_updateChatInputState(transaction: Transaction, peerId: PeerId, threadId: Int64?, inputState: SynchronizeableChatInputState?) { var previousState: InternalChatInterfaceState? - if let peerChatInterfaceState = transaction.getPeerChatInterfaceState(peerId), let data = peerChatInterfaceState.data { - previousState = (try? AdaptedPostboxDecoder().decode(InternalChatInterfaceState.self, from: data)) + if let threadId = threadId { + if let peerChatInterfaceState = transaction.getPeerChatThreadInterfaceState(peerId, threadId: threadId), let data = peerChatInterfaceState.data { + previousState = (try? AdaptedPostboxDecoder().decode(InternalChatInterfaceState.self, from: data)) + } + } else { + if let peerChatInterfaceState = transaction.getPeerChatInterfaceState(peerId), let data = peerChatInterfaceState.data { + previousState = (try? AdaptedPostboxDecoder().decode(InternalChatInterfaceState.self, from: data)) + } } if let updatedStateData = try? AdaptedPostboxEncoder().encode(InternalChatInterfaceState( @@ -100,6 +106,10 @@ func _internal_updateChatInputState(transaction: Transaction, peerId: PeerId, in associatedMessageIds: (inputState?.replyToMessageId).flatMap({ [$0] }) ?? [], data: updatedStateData ) - transaction.setPeerChatInterfaceState(peerId, state: storedState) + if let threadId = threadId { + transaction.setPeerChatThreadInterfaceState(peerId, threadId: threadId, state: storedState) + } else { + transaction.setPeerChatInterfaceState(peerId, state: storedState) + } } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift index 4cc5a1e77c..1963939bc2 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift @@ -22,6 +22,7 @@ public enum StickerPackReference: PostboxCoding, Hashable, Equatable, Codable { case premiumGifts case emojiGenericAnimations case iconStatusEmoji + case iconTopicEmoji public init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("r", orElse: 0) { @@ -84,7 +85,7 @@ public enum StickerPackReference: PostboxCoding, Hashable, Equatable, Codable { encoder.encodeInt32(4, forKey: "r") case .premiumGifts: encoder.encodeInt32(5, forKey: "r") - case .emojiGenericAnimations, .iconStatusEmoji: + case .emojiGenericAnimations, .iconStatusEmoji, .iconTopicEmoji: preconditionFailure() } } @@ -109,7 +110,7 @@ public enum StickerPackReference: PostboxCoding, Hashable, Equatable, Codable { try container.encode(4 as Int32, forKey: "r") case .premiumGifts: try container.encode(5 as Int32, forKey: "r") - case .emojiGenericAnimations, .iconStatusEmoji: + case .emojiGenericAnimations, .iconStatusEmoji, .iconTopicEmoji: preconditionFailure() } } @@ -164,6 +165,12 @@ public enum StickerPackReference: PostboxCoding, Hashable, Equatable, Codable { } else { return false } + case .iconTopicEmoji: + if case .iconTopicEmoji = rhs { + return true + } else { + return false + } } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift index 605365f4a4..4c4b895c0d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift @@ -904,5 +904,29 @@ public extension TelegramEngine.EngineData.Item { } } } + + public struct ThreadData: TelegramEngineDataItem, PostboxViewDataItem { + public typealias Result = MessageHistoryThreadData? + + fileprivate var id: EnginePeer.Id + fileprivate var threadId: Int64 + + public init(id: EnginePeer.Id, threadId: Int64) { + self.id = id + self.threadId = threadId + } + + var key: PostboxViewKey { + return .messageHistoryThreadInfo(peerId: self.id, threadId: self.threadId) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? MessageHistoryThreadInfoView else { + preconditionFailure() + } + + return view.info?.data.get(MessageHistoryThreadData.self) + } + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ClearCloudDrafts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ClearCloudDrafts.swift index e25582447f..c37a0516a9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ClearCloudDrafts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ClearCloudDrafts.swift @@ -9,7 +9,11 @@ func _internal_clearCloudDraftsInteractively(postbox: Postbox, network: Network, |> retryRequest |> mapToSignal { updates -> Signal in return postbox.transaction { transaction -> Signal in - var peerIds = Set() + struct Key: Hashable { + var peerId: PeerId + var threadId: Int64? + } + var keys = Set() switch updates { case let .updates(updates, users, chats, _, _): var peers: [Peer] = [] @@ -26,8 +30,12 @@ func _internal_clearCloudDraftsInteractively(postbox: Postbox, network: Network, } for update in updates { switch update { - case let .updateDraftMessage(_, peer, _, _): - peerIds.insert(peer.peerId) + case let .updateDraftMessage(_, peer, topMsgId, _): + var threadId: Int64? + if let topMsgId = topMsgId { + threadId = Int64(topMsgId) + } + keys.insert(Key(peerId: peer.peerId, threadId: threadId)) default: break } @@ -38,11 +46,17 @@ func _internal_clearCloudDraftsInteractively(postbox: Postbox, network: Network, updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences) var signals: [Signal] = [] - for peerId in peerIds { - _internal_updateChatInputState(transaction: transaction, peerId: peerId, inputState: nil) + for key in keys { + _internal_updateChatInputState(transaction: transaction, peerId: key.peerId, threadId: key.threadId, inputState: nil) - if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { - signals.append(network.request(Api.functions.messages.saveDraft(flags: 0, replyToMsgId: nil, topMsgId: nil, peer: inputPeer, message: "", entities: nil)) + if let peer = transaction.getPeer(key.peerId), let inputPeer = apiInputPeer(peer) { + var flags: Int32 = 0 + var topMsgId: Int32? + if let threadId = key.threadId { + flags |= (1 << 2) + topMsgId = Int32(clamping: threadId) + } + signals.append(network.request(Api.functions.messages.saveDraft(flags: flags, replyToMsgId: nil, topMsgId: topMsgId, peer: inputPeer, message: "", entities: nil)) |> `catch` { _ -> Signal in return .single(.boolFalse) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift index 13dbfb37b9..ee6ed31c6f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift @@ -567,7 +567,7 @@ public struct ChatReplyThreadMessage: Equatable { public var initialAnchor: Anchor public var isNotAvailable: Bool - fileprivate init(messageId: MessageId, channelMessageId: MessageId?, isChannelPost: Bool, isForumPost: Bool, maxMessage: MessageId?, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?, unreadCount: Int, initialFilledHoles: IndexSet, initialAnchor: Anchor, isNotAvailable: Bool) { + public init(messageId: MessageId, channelMessageId: MessageId?, isChannelPost: Bool, isForumPost: Bool, maxMessage: MessageId?, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?, unreadCount: Int, initialFilledHoles: IndexSet, initialAnchor: Anchor, isNotAvailable: Bool) { self.messageId = messageId self.channelMessageId = channelMessageId self.isChannelPost = isChannelPost diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift index 4eb2b67fe2..e17a525135 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift @@ -69,6 +69,11 @@ public enum AdminLogEventAction { case sendMessage(Message) case changeAvailableReactions(previousValue: PeerAllowedReactions, updatedValue: PeerAllowedReactions) case changeUsernames(prev: [String], new: [String]) + case createTopic(info: EngineMessageHistoryThread.Info) + case deleteTopic(info: EngineMessageHistoryThread.Info) + case editTopic(prevInfo: EngineMessageHistoryThread.Info, newInfo: EngineMessageHistoryThread.Info) + case pinTopic(prevInfo: EngineMessageHistoryThread.Info?, newInfo: EngineMessageHistoryThread.Info?) + case toggleForum(isForum: Bool) } public enum ChannelAdminLogEventError { @@ -268,6 +273,62 @@ func channelAdminLogEvents(postbox: Postbox, network: Network, peerId: PeerId, m action = .changeAvailableReactions(previousValue: PeerAllowedReactions(apiReactions: prevValue), updatedValue: PeerAllowedReactions(apiReactions: newValue)) case let .channelAdminLogEventActionChangeUsernames(prevValue, newValue): action = .changeUsernames(prev: prevValue, new: newValue) + case let .channelAdminLogEventActionCreateTopic(topic): + switch topic { + case let .forumTopic(_, _, _, title, iconColor, iconEmojiId, _, _, _, _, _, _, _, _, _): + action = .createTopic(info: EngineMessageHistoryThread.Info(title: title, icon: iconEmojiId, iconColor: iconColor)) + case .forumTopicDeleted: + action = .createTopic(info: EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0)) + } + case let .channelAdminLogEventActionDeleteTopic(topic): + switch topic { + case let .forumTopic(_, _, _, title, iconColor, iconEmojiId, _, _, _, _, _, _, _, _, _): + action = .deleteTopic(info: EngineMessageHistoryThread.Info(title: title, icon: iconEmojiId, iconColor: iconColor)) + case .forumTopicDeleted: + action = .deleteTopic(info: EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0)) + } + case let .channelAdminLogEventActionEditTopic(prevTopic, newTopic): + let prevInfo: EngineMessageHistoryThread.Info + switch prevTopic { + case let .forumTopic(_, _, _, title, iconColor, iconEmojiId, _, _, _, _, _, _, _, _, _): + prevInfo = EngineMessageHistoryThread.Info(title: title, icon: iconEmojiId, iconColor: iconColor) + case .forumTopicDeleted: + prevInfo = EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0) + } + + let newInfo: EngineMessageHistoryThread.Info + switch newTopic { + case let .forumTopic(_, _, _, title, iconColor, iconEmojiId, _, _, _, _, _, _, _, _, _): + newInfo = EngineMessageHistoryThread.Info(title: title, icon: iconEmojiId, iconColor: iconColor) + case .forumTopicDeleted: + newInfo = EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0) + } + + action = .editTopic(prevInfo: prevInfo, newInfo: newInfo) + case let .channelAdminLogEventActionPinTopic(_, prevTopic, newTopic): + let prevInfo: EngineMessageHistoryThread.Info? + switch prevTopic { + case let .forumTopic(_, _, _, title, iconColor, iconEmojiId, _, _, _, _, _, _, _, _, _): + prevInfo = EngineMessageHistoryThread.Info(title: title, icon: iconEmojiId, iconColor: iconColor) + case .forumTopicDeleted: + prevInfo = EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0) + case .none: + prevInfo = nil + } + + let newInfo: EngineMessageHistoryThread.Info? + switch newTopic { + case let .forumTopic(_, _, _, title, iconColor, iconEmojiId, _, _, _, _, _, _, _, _, _): + newInfo = EngineMessageHistoryThread.Info(title: title, icon: iconEmojiId, iconColor: iconColor) + case .forumTopicDeleted: + newInfo = EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0) + case .none: + newInfo = nil + } + + action = .pinTopic(prevInfo: prevInfo, newInfo: newInfo) + case let .channelAdminLogEventActionToggleForum(newValue): + action = .toggleForum(isForum: newValue == .boolTrue) } let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) if let action = action { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/CachedStickerPack.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/CachedStickerPack.swift index 54fce959f9..d683c4dccc 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/CachedStickerPack.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/CachedStickerPack.swift @@ -36,6 +36,9 @@ func cacheStickerPack(transaction: Transaction, info: StickerPackCollectionInfo, case .iconStatusEmoji: namespace = Namespaces.ItemCollection.CloudIconStatusEmoji id = 0 + case .iconTopicEmoji: + namespace = Namespaces.ItemCollection.CloudIconTopicEmoji + id = 0 case .premiumGifts: namespace = Namespaces.ItemCollection.CloudPremiumGifts id = 0 @@ -183,6 +186,20 @@ func _internal_cachedStickerPack(postbox: Postbox, network: Network, reference: } else { return (.fetching, true, nil) } + case .iconTopicEmoji: + let namespace = Namespaces.ItemCollection.CloudIconTopicEmoji + let id: ItemCollectionId.Id = 0 + if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id))))?.get(CachedStickerPack.self), let info = cached.info { + previousHash = cached.hash + let current: CachedStickerPackResult = .result(info, cached.items, false) + if cached.hash != info.hash { + return (current, true, previousHash) + } else { + return (current, false, previousHash) + } + } else { + return (.fetching, true, nil) + } } } |> mapToSignal { result, loadRemote, previousHash in @@ -315,6 +332,18 @@ func cachedStickerPack(transaction: Transaction, reference: StickerPackReference if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id))))?.get(CachedStickerPack.self), let info = cached.info { return (info, cached.items, false) } + case .iconTopicEmoji: + let namespace = Namespaces.ItemCollection.CloudIconTopicEmoji + let id: ItemCollectionId.Id = 0 + if let currentInfo = transaction.getItemCollectionInfo(collectionId: ItemCollectionId(namespace: namespace, id: id)) as? StickerPackCollectionInfo { + let items = transaction.getItemCollectionItems(collectionId: ItemCollectionId(namespace: namespace, id: id)) + if !items.isEmpty { + return (currentInfo, items.compactMap { $0 as? StickerPackItem }, true) + } + } + if let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(ItemCollectionId(namespace: namespace, id: id))))?.get(CachedStickerPack.self), let info = cached.info { + return (info, cached.items, false) + } } return nil } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/LoadedStickerPack.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/LoadedStickerPack.swift index b46d6ed210..7633401323 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/LoadedStickerPack.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/LoadedStickerPack.swift @@ -26,6 +26,8 @@ extension StickerPackReference { return .inputStickerSetEmojiGenericAnimations case .iconStatusEmoji: return .inputStickerSetEmojiDefaultStatuses + case .iconTopicEmoji: + return .inputStickerSetEmojiDefaultTopicIcons } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerSetInstallation.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerSetInstallation.swift index c59aa7e5cf..a6349bf500 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerSetInstallation.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerSetInstallation.swift @@ -52,6 +52,9 @@ func _internal_requestStickerSet(postbox: Postbox, network: Network, reference: case .iconStatusEmoji: collectionId = nil input = .inputStickerSetEmojiDefaultStatuses + case .iconTopicEmoji: + collectionId = nil + input = .inputStickerSetEmojiDefaultTopicIcons } let localSignal: (ItemCollectionId) -> Signal<(ItemCollectionInfo, [ItemCollectionItem])?, NoError> = { collectionId in diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 13253c81d5..fc71ea330c 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -6225,6 +6225,17 @@ public final class EmojiPagerContentComponent: Component { } else if isReactionSelection { orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudTopReactions) orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentReactions) + } else if isTopicIconSelection { + iconStatusEmoji = context.engine.stickers.loadedStickerPack(reference: .iconTopicEmoji, forceActualized: false) + |> map { result -> [TelegramMediaFile] in + switch result { + case let .result(_, items, _): + return items.map(\.file) + default: + return [] + } + } + |> take(1) } let availableReactions: Signal @@ -6293,6 +6304,45 @@ public final class EmojiPagerContentComponent: Component { itemGroupIndexById[groupId] = itemGroups.count itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [resultItem])) } + + var existingIds = Set() + + for file in iconStatusEmoji.prefix(7) { + if existingIds.contains(file.fileId) { + continue + } + existingIds.insert(file.fileId) + + var accentTint = false + for attribute in file.attributes { + if case let .CustomEmoji(_, _, packReference) = attribute { + switch packReference { + case let .id(id, _): + if id == 773947703670341676 || id == 2964141614563343 { + accentTint = true + } + default: + break + } + } + } + + let resultItem: EmojiPagerContentComponent.Item + + let animationData = EntityKeyboardAnimationData(file: file) + resultItem = EmojiPagerContentComponent.Item( + animationData: animationData, + content: .animation(animationData), + itemFile: file, + subgroupId: nil, + icon: .none, + accentTint: accentTint + ) + + if let groupIndex = itemGroupIndexById[groupId] { + itemGroups[groupIndex].items.append(resultItem) + } + } } else if isStatusSelection { let resultItem = EmojiPagerContentComponent.Item( animationData: nil, diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift b/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift index da5ccba729..513dc4c6af 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift @@ -1651,6 +1651,123 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:]) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } + case let .createTopic(info): + //TODO:localize + var peers = SimpleDictionary() + var author: Peer? + if let peer = self.entry.peers[self.entry.event.peerId] { + author = peer + peers[peer.id] = peer + } + var text: String = "" + var entities: [MessageTextEntity] = [] + + let tempString = PresentationStrings.FormattedString(string: "Topic \"\(info.title)\" created", ranges: []) + + appendAttributedText(text: tempString, generateEntities: { index in + if index == 0, let author = author { + return [.TextMention(peerId: author.id)] + } + return [] + }, to: &text, entities: &entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:]) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + case let .deleteTopic(info): + //TODO:localize + var peers = SimpleDictionary() + var author: Peer? + if let peer = self.entry.peers[self.entry.event.peerId] { + author = peer + peers[peer.id] = peer + } + var text: String = "" + var entities: [MessageTextEntity] = [] + + let tempString = PresentationStrings.FormattedString(string: "Topic \"\(info.title)\" deleted", ranges: []) + + appendAttributedText(text: tempString, generateEntities: { index in + if index == 0, let author = author { + return [.TextMention(peerId: author.id)] + } + return [] + }, to: &text, entities: &entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:]) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + case let .editTopic(_, newInfo): + //TODO:localize + var peers = SimpleDictionary() + var author: Peer? + if let peer = self.entry.peers[self.entry.event.peerId] { + author = peer + peers[peer.id] = peer + } + var text: String = "" + var entities: [MessageTextEntity] = [] + + let tempString = PresentationStrings.FormattedString(string: "Topic \"\(newInfo.title)\" edited", ranges: []) + + appendAttributedText(text: tempString, generateEntities: { index in + if index == 0, let author = author { + return [.TextMention(peerId: author.id)] + } + return [] + }, to: &text, entities: &entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:]) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + case let .pinTopic(prevInfo, newInfo): + //TODO:localize + var peers = SimpleDictionary() + var author: Peer? + if let peer = self.entry.peers[self.entry.event.peerId] { + author = peer + peers[peer.id] = peer + } + var text: String = "" + var entities: [MessageTextEntity] = [] + + let tempString: PresentationStrings.FormattedString + if let newInfo = newInfo { + tempString = PresentationStrings.FormattedString(string: "Topic \"\(newInfo.title)\" pinned", ranges: []) + } else if let prevInfo = prevInfo { + tempString = PresentationStrings.FormattedString(string: "Topic \"\(prevInfo.title)\" unpinned", ranges: []) + } else { + tempString = PresentationStrings.FormattedString(string: "Topic unpinned", ranges: []) + } + + appendAttributedText(text: tempString, generateEntities: { index in + if index == 0, let author = author { + return [.TextMention(peerId: author.id)] + } + return [] + }, to: &text, entities: &entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:]) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + case let .toggleForum(isForum): + //TODO:localize + var peers = SimpleDictionary() + var author: Peer? + if let peer = self.entry.peers[self.entry.event.peerId] { + author = peer + peers[peer.id] = peer + } + var text: String = "" + var entities: [MessageTextEntity] = [] + + let tempString = PresentationStrings.FormattedString(string: "Forum \(isForum ? "enabled" : "disabled")", ranges: []) + + appendAttributedText(text: tempString, generateEntities: { index in + if index == 0, let author = author { + return [.TextMention(peerId: author.id)] + } + return [] + }, to: &text, entities: &entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:]) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } } }