From 9633013275bf8d14ba05cf3cdc2aed39aa6e0115 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 23 May 2025 15:56:01 +0800 Subject: [PATCH] Monoforums --- .../Sources/ChatListController.swift | 4 +- .../Sources/ChatListSearchListPaneNode.swift | 6 +- .../Sources/ChatListShimmerNode.swift | 2 +- .../Sources/Node/ChatListItem.swift | 121 +++++++++++------- .../Sources/Node/ChatListNode.swift | 8 +- .../Sources/Node/ChatListNodeLocation.swift | 15 ++- ...MessageHistorySavedMessagesIndexView.swift | 32 ++++- .../Sources/MessageThreadIndexTable.swift | 6 +- .../TextSizeSelectionController.swift | 2 +- .../Themes/ThemePreviewControllerNode.swift | 2 +- submodules/TelegramApi/Sources/Api38.swift | 7 +- .../Account/AccountIntermediateState.swift | 6 +- .../TelegramCore/Sources/ForumChannels.swift | 16 ++- .../State/AccountStateManagementUtils.swift | 29 ++++- ...gedCloudChatRemoveMessagesOperations.swift | 65 ++++++++-- ...yncCore_StandaloneAccountTransaction.swift | 2 +- .../ApplyMaxReadIndexInteractively.swift | 54 ++++++++ .../Messages/TelegramEngineMessages.swift | 7 + ...ChatInlineSearchResultsListComponent.swift | 93 ++++++++++++-- .../Sources/ChatMessageBubbleItemNode.swift | 2 +- .../Sources/ChatMessageItemImpl.swift | 2 +- .../Sources/ChatMessageNotificationItem.swift | 11 +- .../PeerInfoScreenPersonalChannelItem.swift | 4 +- ...aticBusinessMessageListItemComponent.swift | 2 +- .../Sources/QuickReplySetupScreen.swift | 2 +- .../ThemeAccentColorControllerNode.swift | 2 +- .../TelegramUI/Sources/ChatController.swift | 4 +- .../Sources/ChatControllerContentData.swift | 15 ++- .../Sources/ChatControllerNode.swift | 54 ++++---- .../ChatInterfaceStateInputPanels.swift | 2 +- .../ChatInterfaceTitlePanelNodes.swift | 4 +- .../ChatSearchResultsContollerNode.swift | 2 +- .../Sources/ChatTagSearchInputPanelNode.swift | 4 + .../CommandChatInputContextPanelNode.swift | 2 +- 34 files changed, 442 insertions(+), 147 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index c995c060e0..b274ca7099 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1515,11 +1515,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController switch item.index { case .chatList: - if case let .channel(channel) = peer.peer, channel.isForumOrMonoForum { + if case let .channel(channel) = peer.peer, (channel.isForum || (channel.isMonoForum && threadId != nil)) { if let threadId = threadId { let source: ContextContentSource let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .replyThread(message: ChatReplyThreadMessage( - peerId: peer.peerId, threadId: threadId, channelMessageId: nil, isChannelPost: false, isForumPost: true, isMonoforumPost: false, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false + peerId: peer.peerId, threadId: threadId, channelMessageId: nil, isChannelPost: false, isForumPost: !channel.isMonoForum, isMonoforumPost: channel.isMonoForum, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false )), subject: nil, botStart: nil, mode: .standard(.previewing), params: nil) chatController.canReadHistory.set(false) source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index f5b8f850df..64daba37ad 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -1195,7 +1195,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { requiresPremiumForMessaging: requiresPremiumForMessaging, displayAsTopicList: false, tags: [] - )), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enabledContextActions: nil, hiddenOffset: false, interaction: interaction) } case let .messagePlaceholder(_, presentationData, searchScope): var actionTitle: String? @@ -1215,7 +1215,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { let header = ChatListSearchItemHeader(type: .messages(location: nil), theme: presentationData.theme, strings: presentationData.strings, actionTitle: actionTitle, action: { sourceNode in openMessagesFilter(sourceNode) }) - return ChatListItem(presentationData: presentationData, context: context, chatListLocation: location, filterData: nil, index: EngineChatList.Item.Index.chatList(ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: PeerId(0), namespace: Namespaces.Message.Cloud, id: 0), timestamp: 0))), content: .loading, editing: false, hasActiveRevealControls: false, selected: false, header: header, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: presentationData, context: context, chatListLocation: location, filterData: nil, index: EngineChatList.Item.Index.chatList(ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: PeerId(0), namespace: Namespaces.Message.Cloud, id: 0), timestamp: 0))), content: .loading, editing: false, hasActiveRevealControls: false, selected: false, header: header, enabledContextActions: nil, hiddenOffset: false, interaction: interaction) case let .emptyMessagesFooter(presentationData, searchScope, searchQuery): var actionTitle: String? let filterTitle: String @@ -5426,7 +5426,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode { requiresPremiumForMessaging: false, displayAsTopicList: false, tags: [] - )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enabledContextActions: nil, hiddenOffset: false, interaction: interaction) case .media: return nil case .links: diff --git a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift index 7ea984baeb..2cf134e227 100644 --- a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift @@ -219,7 +219,7 @@ public final class ChatListShimmerNode: ASDisplayNode { requiresPremiumForMessaging: false, displayAsTopicList: false, tags: [] - )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enabledContextActions: nil, hiddenOffset: false, interaction: interaction) } var itemNodes: [ChatListItemNode] = [] diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 8c9b818dd9..3f5693a2b0 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -453,6 +453,22 @@ private final class ChatListItemTagListComponent: Component { } public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { + public enum EnabledContextActions { + public struct Actions: OptionSet { + public var rawValue: Int32 + + public init(rawValue: Int32) { + self.rawValue = rawValue + } + + public static let toggleUnread = Actions(rawValue: 1 << 0) + public static let delete = Actions(rawValue: 1 << 1) + } + + case custom(Actions) + case auto + } + let presentationData: ChatListPresentationData let context: AccountContext let chatListLocation: ChatListControllerLocation @@ -462,7 +478,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { let editing: Bool let hasActiveRevealControls: Bool let selected: Bool - let enableContextActions: Bool + let enabledContextActions: EnabledContextActions? let hiddenOffset: Bool let interaction: ChatListNodeInteraction @@ -487,7 +503,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { } } - public init(presentationData: ChatListPresentationData, context: AccountContext, chatListLocation: ChatListControllerLocation, filterData: ChatListItemFilterData?, index: EngineChatList.Item.Index, content: ChatListItemContent, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, header: ListViewItemHeader?, enableContextActions: Bool, hiddenOffset: Bool, interaction: ChatListNodeInteraction) { + public init(presentationData: ChatListPresentationData, context: AccountContext, chatListLocation: ChatListControllerLocation, filterData: ChatListItemFilterData?, index: EngineChatList.Item.Index, content: ChatListItemContent, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, header: ListViewItemHeader?, enabledContextActions: EnabledContextActions?, hiddenOffset: Bool, interaction: ChatListNodeInteraction) { self.presentationData = presentationData self.chatListLocation = chatListLocation self.filterData = filterData @@ -498,7 +514,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { self.hasActiveRevealControls = hasActiveRevealControls self.selected = selected self.header = header - self.enableContextActions = enableContextActions + self.enabledContextActions = enabledContextActions self.hiddenOffset = hiddenOffset self.interaction = interaction } @@ -3493,57 +3509,74 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } } - if item.enableContextActions { - if case .forum = item.chatListLocation { - if case let .chat(itemPeer) = contentPeer, case let .channel(channel) = itemPeer.peer { - var canOpenClose = false - if channel.flags.contains(.isCreator) { - canOpenClose = true - } else if channel.hasPermission(.manageTopics) { - canOpenClose = true - } else if let threadInfo = threadInfo, threadInfo.isOwnedByMe { - canOpenClose = true - } - let canDelete = channel.hasPermission(.deleteAllMessages) - var isClosed = false - if let threadInfo { - isClosed = threadInfo.isClosed - } - if let threadInfo, threadInfo.id == 1 { - peerRevealOptions = forumGeneralRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isMuted: (currentMutedIconImage != nil), isClosed: isClosed, isEditing: item.editing, canOpenClose: canOpenClose, canHide: channel.flags.contains(.isCreator) || channel.hasPermission(.manageTopics), hiddenByDefault: threadInfo.isHidden) + if let enabledContextActions = item.enabledContextActions { + switch enabledContextActions { + case .auto: + if case .forum = item.chatListLocation { + if case let .chat(itemPeer) = contentPeer, case let .channel(channel) = itemPeer.peer { + var canOpenClose = false + if channel.flags.contains(.isCreator) { + canOpenClose = true + } else if channel.hasPermission(.manageTopics) { + canOpenClose = true + } else if let threadInfo = threadInfo, threadInfo.isOwnedByMe { + canOpenClose = true + } + let canDelete = channel.hasPermission(.deleteAllMessages) + var isClosed = false + if let threadInfo { + isClosed = threadInfo.isClosed + } + if let threadInfo, threadInfo.id == 1 { + peerRevealOptions = forumGeneralRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isMuted: (currentMutedIconImage != nil), isClosed: isClosed, isEditing: item.editing, canOpenClose: canOpenClose, canHide: channel.flags.contains(.isCreator) || channel.hasPermission(.manageTopics), hiddenByDefault: threadInfo.isHidden) + } else { + peerRevealOptions = forumThreadRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isMuted: (currentMutedIconImage != nil), isClosed: isClosed, isEditing: item.editing, canOpenClose: canOpenClose, canDelete: canDelete) + } + peerLeftRevealOptions = [] } else { - peerRevealOptions = forumThreadRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isMuted: (currentMutedIconImage != nil), isClosed: isClosed, isEditing: item.editing, canOpenClose: canOpenClose, canDelete: canDelete) + peerRevealOptions = [] + peerLeftRevealOptions = [] } - peerLeftRevealOptions = [] - } else { - peerRevealOptions = [] - peerLeftRevealOptions = [] - } - } else if case .psa = promoInfo { - peerRevealOptions = [ - ItemListRevealOption(key: RevealOptionKey.hidePsa.rawValue, title: item.presentationData.strings.ChatList_HideAction, icon: deleteIcon, color: item.presentationData.theme.list.itemDisclosureActions.inactive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.neutral1.foregroundColor) - ] - peerLeftRevealOptions = [] - } else if case let .peer(peerData) = item.content, let customMessageListData = peerData.customMessageListData { - peerLeftRevealOptions = [] - if customMessageListData.commandPrefix != nil { + } else if case .psa = promoInfo { peerRevealOptions = [ - ItemListRevealOption(key: RevealOptionKey.edit.rawValue, title: item.presentationData.strings.ChatList_ItemMenuEdit, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.neutral2.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.neutral2.foregroundColor), - ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.presentationData.strings.ChatList_ItemMenuDelete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor) + ItemListRevealOption(key: RevealOptionKey.hidePsa.rawValue, title: item.presentationData.strings.ChatList_HideAction, icon: deleteIcon, color: item.presentationData.theme.list.itemDisclosureActions.inactive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.neutral1.foregroundColor) ] + peerLeftRevealOptions = [] + } else if case let .peer(peerData) = item.content, let customMessageListData = peerData.customMessageListData { + peerLeftRevealOptions = [] + if customMessageListData.commandPrefix != nil { + peerRevealOptions = [ + ItemListRevealOption(key: RevealOptionKey.edit.rawValue, title: item.presentationData.strings.ChatList_ItemMenuEdit, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.neutral2.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.neutral2.foregroundColor), + ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.presentationData.strings.ChatList_ItemMenuDelete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor) + ] + } else { + peerRevealOptions = [] + } + } else if promoInfo == nil { + peerRevealOptions = revealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isPinned: isPinned, isMuted: !isAccountPeer ? (currentMutedIconImage != nil) : nil, location: item.chatListLocation, peerId: renderedPeer.peerId, accountPeerId: item.context.account.peerId, canDelete: true, isEditing: item.editing, filterData: item.filterData) + if case let .chat(itemPeer) = contentPeer { + peerLeftRevealOptions = leftRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isUnread: unreadCount.unread, isEditing: item.editing, isPinned: isPinned, isSavedMessages: itemPeer.peerId == item.context.account.peerId, location: item.chatListLocation, peer: itemPeer.peers[itemPeer.peerId]!, filterData: item.filterData) + } else { + peerLeftRevealOptions = [] + } } else { peerRevealOptions = [] - } - } else if promoInfo == nil { - peerRevealOptions = revealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isPinned: isPinned, isMuted: !isAccountPeer ? (currentMutedIconImage != nil) : nil, location: item.chatListLocation, peerId: renderedPeer.peerId, accountPeerId: item.context.account.peerId, canDelete: true, isEditing: item.editing, filterData: item.filterData) - if case let .chat(itemPeer) = contentPeer { - peerLeftRevealOptions = leftRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isUnread: unreadCount.unread, isEditing: item.editing, isPinned: isPinned, isSavedMessages: itemPeer.peerId == item.context.account.peerId, location: item.chatListLocation, peer: itemPeer.peers[itemPeer.peerId]!, filterData: item.filterData) - } else { peerLeftRevealOptions = [] } - } else { + case let .custom(actions): peerRevealOptions = [] peerLeftRevealOptions = [] + + if actions.contains(.toggleUnread) { + if unreadCount.unread { + peerLeftRevealOptions.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: item.presentationData.strings.DialogList_Read, icon: readIcon, color: item.presentationData.theme.list.itemDisclosureActions.inactive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.neutral1.foregroundColor)) + } else { + peerLeftRevealOptions.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: item.presentationData.strings.DialogList_Unread, icon: unreadIcon, color: item.presentationData.theme.list.itemDisclosureActions.accent.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.accent.foregroundColor)) + } + } + if actions.contains(.delete) { + peerRevealOptions.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.presentationData.strings.Common_Delete, icon: deleteIcon, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)) + } } } else { peerRevealOptions = [] diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 80234f5e85..c2c7180505 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -463,7 +463,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, - enableContextActions: true, + enabledContextActions: .auto, hiddenOffset: threadInfo?.isHidden == true && !revealed, interaction: nodeInteraction ), directionHint: entry.directionHint) @@ -709,7 +709,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL hasActiveRevealControls: false, selected: false, header: nil, - enableContextActions: true, + enabledContextActions: .auto, hiddenOffset: groupReferenceEntry.hiddenByDefault && !groupReferenceEntry.revealed, interaction: nodeInteraction ), directionHint: entry.directionHint) @@ -862,7 +862,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, - enableContextActions: true, + enabledContextActions: .auto, hiddenOffset: threadInfo?.isHidden == true && !revealed, interaction: nodeInteraction ), directionHint: entry.directionHint) @@ -1059,7 +1059,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL hasActiveRevealControls: false, selected: false, header: nil, - enableContextActions: true, + enabledContextActions: .auto, hiddenOffset: groupReferenceEntry.hiddenByDefault && !groupReferenceEntry.revealed, interaction: nodeInteraction ), directionHint: entry.directionHint) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift index c999b87ee9..9fc1cca413 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift @@ -362,13 +362,24 @@ public func chatListViewForLocation(chatListLocation: ChatListControllerLocation let mappedMessageIndex = MessageIndex(id: MessageId(peerId: sourceId, namespace: item.index.id.namespace, id: item.index.id.id), timestamp: item.index.timestamp) + let readCounters = EnginePeerReadCounters(state: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: Int32(item.unreadCount), markedUnread: item.markedUnread))]), isMuted: false) + + var itemDraft: EngineChatList.Draft? + if let embeddedState = item.embeddedInterfaceState, let _ = embeddedState.overrideChatTimestamp { + if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) { + if let text = opaqueState.synchronizeableInputState?.text { + itemDraft = EngineChatList.Draft(text: text, entities: opaqueState.synchronizeableInputState?.entities ?? []) + } + } + } + items.append(EngineChatList.Item( id: .chatList(sourceId), index: .chatList(ChatListIndex(pinningIndex: item.pinnedIndex.flatMap(UInt16.init), messageIndex: mappedMessageIndex)), messages: messages, - readCounters: nil, + readCounters: readCounters, isMuted: false, - draft: sourceId == accountPeerId ? draft : nil, + draft: sourceId == accountPeerId ? draft : itemDraft, threadData: nil, renderedPeer: EngineRenderedPeer(peer: EnginePeer(sourcePeer)), presence: nil, diff --git a/submodules/Postbox/Sources/MessageHistorySavedMessagesIndexView.swift b/submodules/Postbox/Sources/MessageHistorySavedMessagesIndexView.swift index e41b1974bc..4854403842 100644 --- a/submodules/Postbox/Sources/MessageHistorySavedMessagesIndexView.swift +++ b/submodules/Postbox/Sources/MessageHistorySavedMessagesIndexView.swift @@ -8,6 +8,8 @@ final class MutableMessageHistorySavedMessagesIndexView: MutablePostboxView { let index: MessageIndex let topMessage: Message? let unreadCount: Int + let markedUnread: Bool + let embeddedInterfaceState: StoredPeerChatInterfaceState? init( id: Int64, @@ -15,7 +17,9 @@ final class MutableMessageHistorySavedMessagesIndexView: MutablePostboxView { pinnedIndex: Int?, index: MessageIndex, topMessage: Message?, - unreadCount: Int + unreadCount: Int, + markedUnread: Bool, + embeddedInterfaceState: StoredPeerChatInterfaceState? ) { self.id = id self.peer = peer @@ -23,6 +27,8 @@ final class MutableMessageHistorySavedMessagesIndexView: MutablePostboxView { self.index = index self.topMessage = topMessage self.unreadCount = unreadCount + self.markedUnread = markedUnread + self.embeddedInterfaceState = embeddedInterfaceState } } @@ -65,13 +71,17 @@ final class MutableMessageHistorySavedMessagesIndexView: MutablePostboxView { pinnedIndex = index } + let embeddedInterfaceState = postbox.peerChatThreadInterfaceStateTable.get(PeerChatThreadId(peerId: self.peerId, threadId: item.threadId)) + self.items.append(Item( id: item.threadId, peer: postbox.peerTable.get(PeerId(item.threadId)), pinnedIndex: pinnedIndex, index: item.index, topMessage: postbox.getMessage(item.index.id), - unreadCount: Int(item.info.summary.totalUnreadCount) + unreadCount: Int(item.info.summary.totalUnreadCount), + markedUnread: item.info.summary.isMarkedUnread, + embeddedInterfaceState: embeddedInterfaceState )) } @@ -125,6 +135,8 @@ public final class EngineMessageHistorySavedMessagesThread { public let index: MessageIndex public let topMessage: Message? public let unreadCount: Int + public let markedUnread: Bool + public let embeddedInterfaceState: StoredPeerChatInterfaceState? public init( id: Int64, @@ -132,7 +144,9 @@ public final class EngineMessageHistorySavedMessagesThread { pinnedIndex: Int?, index: MessageIndex, topMessage: Message?, - unreadCount: Int + unreadCount: Int, + markedUnread: Bool, + embeddedInterfaceState: StoredPeerChatInterfaceState? ) { self.id = id self.peer = peer @@ -140,6 +154,8 @@ public final class EngineMessageHistorySavedMessagesThread { self.index = index self.topMessage = topMessage self.unreadCount = unreadCount + self.markedUnread = markedUnread + self.embeddedInterfaceState = embeddedInterfaceState } public static func ==(lhs: Item, rhs: Item) -> Bool { @@ -168,6 +184,12 @@ public final class EngineMessageHistorySavedMessagesThread { if lhs.unreadCount != rhs.unreadCount { return false } + if lhs.markedUnread != rhs.markedUnread { + return false + } + if lhs.embeddedInterfaceState != rhs.embeddedInterfaceState { + return false + } return true } @@ -190,7 +212,9 @@ public final class MessageHistorySavedMessagesIndexView: PostboxView { pinnedIndex: item.pinnedIndex, index: item.index, topMessage: item.topMessage, - unreadCount: item.unreadCount + unreadCount: item.unreadCount, + markedUnread: item.markedUnread, + embeddedInterfaceState: item.embeddedInterfaceState )) } self.items = items diff --git a/submodules/Postbox/Sources/MessageThreadIndexTable.swift b/submodules/Postbox/Sources/MessageThreadIndexTable.swift index 36cf7be63f..9e3c2aec2d 100644 --- a/submodules/Postbox/Sources/MessageThreadIndexTable.swift +++ b/submodules/Postbox/Sources/MessageThreadIndexTable.swift @@ -3,20 +3,24 @@ import Foundation public struct StoredMessageHistoryThreadInfo: Equatable, PostboxCoding { public struct Summary: Equatable, PostboxCoding { public var totalUnreadCount: Int32 + public var isMarkedUnread: Bool public var mutedUntil: Int32? - public init(totalUnreadCount: Int32, mutedUntil: Int32?) { + public init(totalUnreadCount: Int32, isMarkedUnread: Bool, mutedUntil: Int32?) { self.totalUnreadCount = totalUnreadCount + self.isMarkedUnread = isMarkedUnread self.mutedUntil = mutedUntil } public init(decoder: PostboxDecoder) { self.totalUnreadCount = decoder.decodeInt32ForKey("u", orElse: 0) self.mutedUntil = decoder.decodeOptionalInt32ForKey("m") + self.isMarkedUnread = decoder.decodeBoolForKey("mu", orElse: false) } public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.totalUnreadCount, forKey: "u") + encoder.encodeBool(self.isMarkedUnread, forKey: "mu") if let mutedUntil = self.mutedUntil { encoder.encodeInt32(mutedUntil, forKey: "m") } else { diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 4258e68693..7ccaa2c03e 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -312,7 +312,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView hasActiveRevealControls: false, selected: false, header: nil, - enableContextActions: false, + enabledContextActions: nil, hiddenOffset: false, interaction: interaction ) diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 5431653217..e1456944e4 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -459,7 +459,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate { hasActiveRevealControls: false, selected: false, header: nil, - enableContextActions: false, + enabledContextActions: nil, hiddenOffset: false, interaction: interaction ) diff --git a/submodules/TelegramApi/Sources/Api38.swift b/submodules/TelegramApi/Sources/Api38.swift index 0cda8cfd07..c602f4ff7f 100644 --- a/submodules/TelegramApi/Sources/Api38.swift +++ b/submodules/TelegramApi/Sources/Api38.swift @@ -5364,15 +5364,16 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func deleteSavedHistory(flags: Int32, peer: Api.InputPeer, maxId: Int32, minDate: Int32?, maxDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func deleteSavedHistory(flags: Int32, parentPeer: Api.InputPeer?, peer: Api.InputPeer, maxId: Int32, minDate: Int32?, maxDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1855459371) + buffer.appendInt32(1304758367) serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {parentPeer!.serialize(buffer, true)} peer.serialize(buffer, true) serializeInt32(maxId, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 2) != 0 {serializeInt32(minDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 3) != 0 {serializeInt32(maxDate!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.deleteSavedHistory", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("maxId", String(describing: maxId)), ("minDate", String(describing: minDate)), ("maxDate", String(describing: maxDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + return (FunctionDescription(name: "messages.deleteSavedHistory", parameters: [("flags", String(describing: flags)), ("parentPeer", String(describing: parentPeer)), ("peer", String(describing: peer)), ("maxId", String(describing: maxId)), ("minDate", String(describing: minDate)), ("maxDate", String(describing: maxDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in let reader = BufferReader(buffer) var result: Api.messages.AffectedHistory? if let signature = reader.readInt32() { diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index 6f2fe299c1..53e4d60cfd 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -73,7 +73,7 @@ enum AccountStateMutationOperation { case ReadOutbox(MessageId, Int32?) case ResetReadState(peerId: PeerId, namespace: MessageId.Namespace, maxIncomingReadId: MessageId.Id, maxOutgoingReadId: MessageId.Id, maxKnownId: MessageId.Id, count: Int32, markedUnread: Bool?) case ResetIncomingReadState(groupId: PeerGroupId, peerId: PeerId, namespace: MessageId.Namespace, maxIncomingReadId: MessageId.Id, count: Int32, pts: Int32) - case UpdatePeerChatUnreadMark(PeerId, MessageId.Namespace, Bool) + case UpdatePeerChatUnreadMark(PeerId, Int64?, MessageId.Namespace, Bool) case ResetMessageTagSummary(PeerId, MessageTags, MessageId.Namespace, Int32, MessageHistoryTagNamespaceCountValidityRange) case ReadGroupFeedInbox(PeerGroupId, MessageIndex) case UpdateState(AuthorizedAccountState.State) @@ -424,8 +424,8 @@ struct AccountMutableState { self.addOperation(.ResetIncomingReadState(groupId: groupId, peerId: peerId, namespace: namespace, maxIncomingReadId: maxIncomingReadId, count: count, pts: pts)) } - mutating func updatePeerChatUnreadMark(_ peerId: PeerId, namespace: MessageId.Namespace, value: Bool) { - self.addOperation(.UpdatePeerChatUnreadMark(peerId, namespace, value)) + mutating func updatePeerChatUnreadMark(_ peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, value: Bool) { + self.addOperation(.UpdatePeerChatUnreadMark(peerId, threadId, namespace, value)) } mutating func resetMessageTagSummary(_ peerId: PeerId, tag: MessageTags, namespace: MessageId.Namespace, count: Int32, range: MessageHistoryTagNamespaceCountValidityRange) { diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index a4e8f5dfa4..96ca46a17a 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -67,6 +67,7 @@ public struct MessageHistoryThreadData: Codable, Equatable { case isClosed case isHidden case notificationSettings + case isMarkedUnread } public var creationDate: Int32 @@ -74,7 +75,8 @@ public struct MessageHistoryThreadData: Codable, Equatable { public var author: PeerId public var info: EngineMessageHistoryThread.Info public var incomingUnreadCount: Int32 - public var maxIncomingReadId: Int32 + public var isMarkedUnread: Bool + public var maxIncomingReadId: Int32 public var maxKnownMessageId: Int32 public var maxOutgoingReadId: Int32 public var isClosed: Bool @@ -87,6 +89,7 @@ public struct MessageHistoryThreadData: Codable, Equatable { author: PeerId, info: EngineMessageHistoryThread.Info, incomingUnreadCount: Int32, + isMarkedUnread: Bool, maxIncomingReadId: Int32, maxKnownMessageId: Int32, maxOutgoingReadId: Int32, @@ -99,6 +102,7 @@ public struct MessageHistoryThreadData: Codable, Equatable { self.author = author self.info = info self.incomingUnreadCount = incomingUnreadCount + self.isMarkedUnread = isMarkedUnread self.maxIncomingReadId = maxIncomingReadId self.maxKnownMessageId = maxKnownMessageId self.maxOutgoingReadId = maxOutgoingReadId @@ -115,6 +119,7 @@ public struct MessageHistoryThreadData: Codable, Equatable { self.author = try container.decode(PeerId.self, forKey: .author) self.info = try container.decode(EngineMessageHistoryThread.Info.self, forKey: .info) self.incomingUnreadCount = try container.decode(Int32.self, forKey: .incomingUnreadCount) + self.isMarkedUnread = try container.decodeIfPresent(Bool.self, forKey: .isMarkedUnread) ?? false self.maxIncomingReadId = try container.decode(Int32.self, forKey: .maxIncomingReadId) self.maxKnownMessageId = try container.decode(Int32.self, forKey: .maxKnownMessageId) self.maxOutgoingReadId = try container.decode(Int32.self, forKey: .maxOutgoingReadId) @@ -131,6 +136,7 @@ public struct MessageHistoryThreadData: Codable, Equatable { try container.encode(self.author, forKey: .author) try container.encode(self.info, forKey: .info) try container.encode(self.incomingUnreadCount, forKey: .incomingUnreadCount) + try container.encode(self.isMarkedUnread, forKey: .isMarkedUnread) try container.encode(self.maxIncomingReadId, forKey: .maxIncomingReadId) try container.encode(self.maxKnownMessageId, forKey: .maxKnownMessageId) try container.encode(self.maxOutgoingReadId, forKey: .maxOutgoingReadId) @@ -154,6 +160,7 @@ extension StoredMessageHistoryThreadInfo { } self.init(data: entry, summary: Summary( totalUnreadCount: data.incomingUnreadCount, + isMarkedUnread: data.isMarkedUnread, mutedUntil: mutedUntil )) } @@ -654,6 +661,7 @@ public func _internal_fillSavedMessageHistory(accountPeerId: PeerId, postbox: Po author: accountPeerId, info: EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0), incomingUnreadCount: 0, + isMarkedUnread: false, maxIncomingReadId: 0, maxKnownMessageId: 0, maxOutgoingReadId: 0, @@ -771,6 +779,7 @@ func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Post iconColor: 0 ), incomingUnreadCount: 0, + isMarkedUnread: false, maxIncomingReadId: 0, maxKnownMessageId: topMessage, maxOutgoingReadId: 0, @@ -812,7 +821,8 @@ func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Post index: topicIndex, threadPeer: threadPeer )) - case let .monoForumDialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _): + case let .monoForumDialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _): + let isMarkedUnread = (flags & (1 << 3)) != 0 let data = MessageHistoryThreadData( creationDate: 0, isOwnedByMe: true, @@ -823,6 +833,7 @@ func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Post iconColor: 0 ), incomingUnreadCount: unreadCount, + isMarkedUnread: isMarkedUnread, maxIncomingReadId: readInboxMaxId, maxKnownMessageId: topMessage, maxOutgoingReadId: readOutboxMaxId, @@ -971,6 +982,7 @@ func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Post iconColor: iconColor ), incomingUnreadCount: unreadCount, + isMarkedUnread: false, maxIncomingReadId: readInboxMaxId, maxKnownMessageId: topMessage, maxOutgoingReadId: readOutboxMaxId, diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 8091f87215..cf5a42468c 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -1224,11 +1224,11 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: switch peer { case let .dialogPeer(peer): let peerId = peer.peerId - //TODO:release if let savedPeerId { + updatedState.updatePeerChatUnreadMark(peerId, threadId: savedPeerId.peerId.toInt64(), namespace: Namespaces.Message.Cloud, value: (flags & (1 << 0)) != 0) let _ = savedPeerId } else { - updatedState.updatePeerChatUnreadMark(peerId, namespace: Namespaces.Message.Cloud, value: (flags & (1 << 0)) != 0) + updatedState.updatePeerChatUnreadMark(peerId, threadId: nil, namespace: Namespaces.Message.Cloud, value: (flags & (1 << 0)) != 0) } case .dialogPeerFolder: break @@ -2080,6 +2080,7 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM iconColor: iconColor ), incomingUnreadCount: unreadCount, + isMarkedUnread: false, maxIncomingReadId: readInboxMaxId, maxKnownMessageId: topMessage, maxOutgoingReadId: readOutboxMaxId, @@ -2098,7 +2099,7 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM } case let .savedDialog(savedDialog): switch savedDialog { - case let .monoForumDialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _): + case let .monoForumDialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _): state.operations.append(.ResetForumTopic( topicId: PeerAndBoundThreadId(peerId: peerId, threadId: peer.peerId.toInt64()), @@ -2113,6 +2114,7 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM iconColor: 0 ), incomingUnreadCount: unreadCount, + isMarkedUnread: (flags & (1 << 3)) != 0, maxIncomingReadId: readInboxMaxId, maxKnownMessageId: topMessage, maxOutgoingReadId: readOutboxMaxId, @@ -2229,6 +2231,7 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM iconColor: iconColor ), incomingUnreadCount: unreadCount, + isMarkedUnread: false, maxIncomingReadId: readInboxMaxId, maxKnownMessageId: topMessage, maxOutgoingReadId: readOutboxMaxId, @@ -2247,7 +2250,7 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM } case let .savedDialog(savedDialog): switch savedDialog { - case let .monoForumDialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _): + case let .monoForumDialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _): let data = MessageHistoryThreadData( creationDate: 0, isOwnedByMe: true, @@ -2258,6 +2261,7 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM iconColor: 0 ), incomingUnreadCount: unreadCount, + isMarkedUnread: (flags & (1 << 3)) != 0, maxIncomingReadId: readInboxMaxId, maxKnownMessageId: topMessage, maxOutgoingReadId: readOutboxMaxId, @@ -2381,6 +2385,7 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM iconColor: iconColor ), incomingUnreadCount: unreadCount, + isMarkedUnread: false, maxIncomingReadId: readInboxMaxId, maxKnownMessageId: topMessage, maxOutgoingReadId: readOutboxMaxId, @@ -2397,7 +2402,7 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM } case let .savedDialog(savedDialog): switch savedDialog { - case let .monoForumDialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _): + case let .monoForumDialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _): fetchedChatList.threadInfos[PeerAndBoundThreadId(peerId: peerId, threadId: peer.peerId.toInt64())] = StoreMessageHistoryThreadData( data: MessageHistoryThreadData( @@ -2410,6 +2415,7 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM iconColor: 0 ), incomingUnreadCount: unreadCount, + isMarkedUnread: (flags & (1 << 3)) != 0, maxIncomingReadId: readInboxMaxId, maxKnownMessageId: topMessage, maxOutgoingReadId: readOutboxMaxId, @@ -4360,8 +4366,17 @@ func replayFinalState( transaction.setNeedsIncomingReadStateSynchronization(peerId) invalidateGroupStats.insert(groupId) } - case let .UpdatePeerChatUnreadMark(peerId, namespace, value): - transaction.applyMarkUnread(peerId: peerId, namespace: namespace, value: value, interactive: false) + case let .UpdatePeerChatUnreadMark(peerId, threadId, namespace, value): + if let threadId { + if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { + data.isMarkedUnread = value + if let entry = StoredMessageHistoryThreadInfo(data) { + transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: threadId, info: entry) + } + } + } else { + transaction.applyMarkUnread(peerId: peerId, namespace: namespace, value: value, interactive: false) + } case let .ResetMessageTagSummary(peerId, tag, namespace, count, range): transaction.replaceMessageTagSummary(peerId: peerId, threadId: nil, tagMask: tag, namespace: namespace, customTag: nil, count: count, maxId: range.maxId) if count == 0 { diff --git a/submodules/TelegramCore/Sources/State/ManagedCloudChatRemoveMessagesOperations.swift b/submodules/TelegramCore/Sources/State/ManagedCloudChatRemoveMessagesOperations.swift index 18743c8450..6368dba736 100644 --- a/submodules/TelegramCore/Sources/State/ManagedCloudChatRemoveMessagesOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedCloudChatRemoveMessagesOperations.swift @@ -453,7 +453,7 @@ private func _internal_clearHistory(transaction: Transaction, postbox: Postbox, flags |= 1 << 3 updatedMaxId = 0 } - let signal = network.request(Api.functions.messages.deleteSavedHistory(flags: flags, peer: inputSubPeer, maxId: updatedMaxId, minDate: operation.minTimestamp, maxDate: operation.maxTimestamp)) + let signal = network.request(Api.functions.messages.deleteSavedHistory(flags: flags, parentPeer: nil, peer: inputSubPeer, maxId: updatedMaxId, minDate: operation.minTimestamp, maxDate: operation.maxTimestamp)) |> map { result -> Api.messages.AffectedHistory? in return result } @@ -490,15 +490,62 @@ private func _internal_clearHistory(transaction: Transaction, postbox: Postbox, return .complete() } else { if let threadId = operation.threadId { - return network.request(Api.functions.channels.deleteTopicHistory(channel: inputChannel, topMsgId: Int32(clamping: threadId))) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> mapToSignal { result -> Signal in - if let _ = result { + if peer.isMonoForum { + guard let inputPeer = apiInputPeer(peer) else { + return .complete() + } + guard let inputSubPeer = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer) else { + return .complete() + } + + var flags: Int32 = 0 + var updatedMaxId = operation.topMessageId.id + if operation.minTimestamp != nil { + flags |= 1 << 2 + updatedMaxId = 0 + } + if operation.maxTimestamp != nil { + flags |= 1 << 3 + updatedMaxId = 0 + } + flags |= 1 << 0 + let signal = network.request(Api.functions.messages.deleteSavedHistory(flags: flags, parentPeer: inputPeer, peer: inputSubPeer, maxId: updatedMaxId, minDate: operation.minTimestamp, maxDate: operation.maxTimestamp)) + |> map { result -> Api.messages.AffectedHistory? in + return result + } + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + if let result = result { + switch result { + case let .affectedHistory(pts, ptsCount, offset): + stateManager.addUpdateGroups([.updatePts(pts: pts, ptsCount: ptsCount)]) + if offset == 0 { + return .fail(true) + } else { + return .complete() + } + } + } else { + return .fail(true) + } + } + return (signal |> restart) + |> `catch` { _ -> Signal in + return .complete() + } + } else { + return network.request(Api.functions.channels.deleteTopicHistory(channel: inputChannel, topMsgId: Int32(clamping: threadId))) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + if let _ = result { + } + return .complete() } - return .complete() } } else { var flags: Int32 = 0 diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift index d5af6a3b65..e9bd0743d6 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift @@ -190,7 +190,7 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = { }, automaticThreadIndexInfo: { peerId, _ in if peerId.namespace == Namespaces.Peer.CloudUser { - return StoredMessageHistoryThreadInfo(data: CodableEntry(data: Data()), summary: StoredMessageHistoryThreadInfo.Summary(totalUnreadCount: 0, mutedUntil: nil)) + return StoredMessageHistoryThreadInfo(data: CodableEntry(data: Data()), summary: StoredMessageHistoryThreadInfo.Summary(totalUnreadCount: 0, isMarkedUnread: false, mutedUntil: nil)) } else { return nil } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift index 49660cece5..596614cfe2 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift @@ -115,6 +115,58 @@ func _internal_togglePeerUnreadMarkInteractively(postbox: Postbox, network: Netw } } +func _internal_toggleForumThreadUnreadMarkInteractively(transaction: Transaction, network: Network, viewTracker: AccountViewTracker, peerId: PeerId, threadId: Int64, setToValue: Bool?) { + guard let peer = transaction.getPeer(peerId) else { + return + } + guard let channel = peer as? TelegramChannel, channel.isForumOrMonoForum else { + return + } + guard var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) else { + return + } + guard let messageIndex = transaction.getMessageHistoryThreadTopMessage(peerId: peerId, threadId: threadId, namespaces: Set([Namespaces.Message.Cloud])) else { + return + } + + let setToValue = setToValue ?? !(data.incomingUnreadCount != 0 || data.isMarkedUnread) + + if setToValue { + data.isMarkedUnread = true + if let entry = StoredMessageHistoryThreadInfo(data) { + transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: threadId, info: entry) + } + + if channel.flags.contains(.isForum) { + } 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.markDialogUnread(flags: 1 << 0, parentPeer: inputPeer, peer: .inputDialogPeer(peer: subPeer))).start() + } + } + } else { + if data.incomingUnreadCount != 0 || data.isMarkedUnread { + data.incomingUnreadCount = 0 + data.isMarkedUnread = false + 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: 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: 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_markForumThreadAsReadInteractively(transaction: Transaction, network: Network, viewTracker: AccountViewTracker, peerId: PeerId, threadId: Int64) { guard let peer = transaction.getPeer(peerId) else { return @@ -130,6 +182,7 @@ func _internal_markForumThreadAsReadInteractively(transaction: Transaction, netw } if data.incomingUnreadCount != 0 { data.incomingUnreadCount = 0 + data.isMarkedUnread = false data.maxIncomingReadId = max(messageIndex.id.id, data.maxIncomingReadId) data.maxKnownMessageId = max(data.maxKnownMessageId, messageIndex.id.id) @@ -169,6 +222,7 @@ func _internal_togglePeerUnreadMarkInteractively(transaction: Transaction, netwo } if data.incomingUnreadCount != 0 { data.incomingUnreadCount = 0 + data.isMarkedUnread = false data.maxIncomingReadId = max(messageIndex.id.id, data.maxIncomingReadId) data.maxKnownMessageId = max(data.maxKnownMessageId, messageIndex.id.id) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index c94240fce7..f1e29ea204 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -740,6 +740,13 @@ public extension TelegramEngine { |> ignoreValues } + public func togglePeerUnreadMarkInteractively(peerId: EnginePeer.Id, threadId: Int64, setToValue: Bool?) -> Signal { + return self.account.postbox.transaction { transaction -> Void in + _internal_toggleForumThreadUnreadMarkInteractively(transaction: transaction, network: self.account.network, viewTracker: self.account.viewTracker, peerId: peerId, threadId: threadId, setToValue: setToValue) + } + |> ignoreValues + } + public func markForumThreadAsRead(peerId: EnginePeer.Id, threadId: Int64) -> Signal { return self.account.postbox.transaction { transaction -> Void in _internal_markForumThreadAsReadInteractively(transaction: transaction, network: self.account.network, viewTracker: self.account.viewTracker, peerId: peerId, threadId: threadId) diff --git a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift index 24bc6ed9ed..ba8e6418c3 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift @@ -277,6 +277,9 @@ public final class ChatInlineSearchResultsListComponent: Component { return self.isReadyPromise.get() } + private var hintDeletedChats = Set() + private var hintAnimateListTransition: Bool = false + override public init(frame: CGRect) { self.listNode = ListView() @@ -330,6 +333,34 @@ public final class ChatInlineSearchResultsListComponent: Component { } } + private func performDeleteAction(peerId: EnginePeer.Id, threadId: Int64?) { + guard let component = self.component else { + return + } + guard let mainPeerId = component.peerId else { + return + } + if case .monoforumChats = component.contents { + let threadId = threadId ?? peerId.toInt64() + + self.hintDeletedChats.insert(peerId) + let _ = component.context.engine.peers.removeForumChannelThread(id: mainPeerId, threadId: threadId).startStandalone() + } + } + + private func performToggleUnreadAction(peerId: EnginePeer.Id, threadId: Int64?) { + guard let component = self.component else { + return + } + guard let mainPeerId = component.peerId, case .monoforumChats = component.contents else { + return + } + + let threadId = threadId ?? peerId.toInt64() + + let _ = component.context.engine.messages.togglePeerUnreadMarkInteractively(peerId: mainPeerId, threadId: threadId, setToValue: nil).startStandalone() + } + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { guard let result = super.hitTest(point, with: event) else { return nil @@ -655,9 +686,33 @@ public final class ChatInlineSearchResultsListComponent: Component { let contentsId = self.nextContentsId self.nextContentsId += 1 + + let contentId: ContentsState.ContentId = .search(query) + + if let previousContentsState = self.contentsState, previousContentsState.contentId == contentId { + for deletedPeerId in self.hintDeletedChats { + if previousContentsState.entries.contains(where: { entry in + if case let .chat(id) = entry.id, case .chatList(deletedPeerId) = id { + return true + } + return false + }) && !entries.contains(where: { entry in + if case let .chat(id) = entry.id, case .chatList(deletedPeerId) = id { + return true + } + return false + }) { + self.hintAnimateListTransition = true + break + } + } + } + + self.hintDeletedChats.removeAll() + self.contentsState = ContentsState( id: contentsId, - contentId: .search(query), + contentId: contentId, entries: entries, messages: messages, hasEarlier: false, //!(result?.completed ?? true), @@ -783,9 +838,17 @@ public final class ChatInlineSearchResultsListComponent: Component { }, setPeerThreadMuted: { _, _, _ in }, - deletePeer: { _, _ in + deletePeer: { [weak self] peerId, _ in + guard let self else { + return + } + self.performDeleteAction(peerId: peerId, threadId: nil) }, - deletePeerThread: { _, _ in + deletePeerThread: { [weak self] peerId, threadId in + guard let self else { + return + } + self.performDeleteAction(peerId: peerId, threadId: nil) }, setPeerThreadStopped: { _, _, _ in }, @@ -795,7 +858,11 @@ public final class ChatInlineSearchResultsListComponent: Component { }, updatePeerGrouping: { _, _ in }, - togglePeerMarkedUnread: { _, _ in + togglePeerMarkedUnread: { [weak self] peerId, _ in + guard let self else { + return + } + self.performToggleUnreadAction(peerId: peerId, threadId: nil) }, toggleArchivedFolderHiddenByDefault: { }, @@ -960,7 +1027,7 @@ public final class ChatInlineSearchResultsListComponent: Component { return ChatListItem( presentationData: chatListPresentationData, context: component.context, - chatListLocation: component.peerId == component.context.account.peerId ? .savedMessagesChats(peerId: component.context.account.peerId) : .chatList(groupId: .root), + chatListLocation: .savedMessagesChats(peerId: component.peerId ?? component.context.account.peerId), filterData: nil, index: .forum( pinnedIndex: .none, @@ -997,7 +1064,7 @@ public final class ChatInlineSearchResultsListComponent: Component { hasActiveRevealControls: false, selected: false, header: displayMessagesHeader ? ChatListSearchItemHeader(type: .messages(location: nil), theme: listPresentationData.theme, strings: listPresentationData.strings) : nil, - enableContextActions: false, + enabledContextActions: nil, hiddenOffset: false, interaction: chatListNodeInteraction ) @@ -1012,12 +1079,12 @@ public final class ChatInlineSearchResultsListComponent: Component { messages: item.messages, peer: item.renderedPeer, threadInfo: nil, - combinedReadState: nil, + combinedReadState: item.readCounters, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, - draftState: nil, + draftState: item.draft.flatMap(ChatListItemContent.DraftState.init(draft:)), mediaDraftContentType: nil, inputActivities: nil, promoInfo: nil, @@ -1036,7 +1103,7 @@ public final class ChatInlineSearchResultsListComponent: Component { hasActiveRevealControls: false, selected: false, header: nil, - enableContextActions: false, + enabledContextActions: .custom([.toggleUnread, .delete]), hiddenOffset: false, interaction: chatListNodeInteraction ) @@ -1044,6 +1111,7 @@ public final class ChatInlineSearchResultsListComponent: Component { } var scrollToItem: ListViewScrollToItem? + var listTransactionOptions: ListViewDeleteAndInsertOptions = [.Synchronous, .LowLatency, .PreferSynchronousDrawing, .PreferSynchronousResourceLoading] if previousContentsState?.contentId != contentsState.contentId && !contentsState.entries.isEmpty { scrollToItem = ListViewScrollToItem( index: 0, @@ -1054,6 +1122,11 @@ public final class ChatInlineSearchResultsListComponent: Component { ) } + if previousContentsState?.contentId == contentsState.contentId && self.hintAnimateListTransition { + listTransactionOptions.insert(.AnimateInsertion) + } + self.hintAnimateListTransition = false + self.listNode.transaction( deleteIndices: deleteIndices.map { index in return ListViewDeleteItem(index: index, directionHint: nil) @@ -1075,7 +1148,7 @@ public final class ChatInlineSearchResultsListComponent: Component { directionHint: nil ) }, - options: [.Synchronous, .LowLatency, .PreferSynchronousDrawing, .PreferSynchronousResourceLoading], + options: listTransactionOptions, scrollToItem: scrollToItem, updateSizeAndInsets: nil, updateOpaqueState: contentsState.id diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 802dcf7898..97f6229ea4 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, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = item.content.firstMessage.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.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.hasPermission(.sendSomething), 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 0a7faa26da..454da26df0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift @@ -327,7 +327,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible } } else { if channel.isMonoForum { - if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = content.firstMessage.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { + if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = content.firstMessage.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething) { headerSeparableThreadId = content.firstMessage.threadId if let threadId = content.firstMessage.threadId, let peer = content.firstMessage.peers[EnginePeer.Id(threadId)] { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift index 8aba6f3d01..772f6cbde2 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift @@ -138,10 +138,15 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { authorString = EnginePeer(author).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) } - if let threadData = item.threadData { - title = "\(authorString) → \(threadData.info.title)" + if case let .channel(channel) = peer, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = firstMessage.peers[linkedMonoforumId] { + //TODO:localize + title = authorString + "@" + EnginePeer(mainChannel).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + " Messages" } else { - title = authorString + "@" + peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + if let threadData = item.threadData { + title = "\(authorString) → \(threadData.info.title)" + } else { + title = authorString + "@" + peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + } } } else { title = peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift index 3f174f7ecc..55abf78cd1 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift @@ -244,7 +244,7 @@ public final class LoadingOverlayNode: ASDisplayNode { requiresPremiumForMessaging: false, displayAsTopicList: false, tags: [] - )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + )), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enabledContextActions: nil, hiddenOffset: false, interaction: interaction) } var itemNodes: [ChatListItemNode] = [] @@ -616,7 +616,7 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod hasActiveRevealControls: false, selected: false, header: nil, - enableContextActions: false, + enabledContextActions: nil, hiddenOffset: false, interaction: chatListNodeInteraction ) diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift index 92c3c48ba6..c293113855 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift @@ -264,7 +264,7 @@ final class GreetingMessageListItemComponent: Component { hasActiveRevealControls: false, selected: false, header: nil, - enableContextActions: false, + enabledContextActions: nil, hiddenOffset: false, interaction: chatListNodeInteraction ) diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift index 3863825ab4..67c0665476 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift @@ -294,7 +294,7 @@ final class QuickReplySetupScreenComponent: Component { hasActiveRevealControls: false, selected: isSelected, header: nil, - enableContextActions: true, + enabledContextActions: .auto, hiddenOffset: false, interaction: chatListNodeInteraction ) diff --git a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift index 24fad05cc3..6e3d99c64a 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift @@ -954,7 +954,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate hasActiveRevealControls: false, selected: false, header: nil, - enableContextActions: false, + enabledContextActions: nil, hiddenOffset: false, interaction: interaction ) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index ad2de43f0f..f0be6dad05 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4761,7 +4761,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let self else { return } - let defaultDirection: ChatControllerAnimateInnerChatSwitchDirection? = self.chatDisplayNode.chatLocationTabSwitchDirection(from: self.chatLocation.threadId, to: self.presentationInterfaceState.chatLocation.threadId).flatMap { direction -> ChatControllerAnimateInnerChatSwitchDirection in + let defaultDirection: ChatControllerAnimateInnerChatSwitchDirection? = self.chatDisplayNode.chatLocationTabSwitchDirection(from: self.chatLocation.threadId, to: threadId).flatMap { direction -> ChatControllerAnimateInnerChatSwitchDirection in return direction ? .right : .left } self.updateChatLocationThread(threadId: threadId, animationDirection: animationDirection ?? defaultDirection) @@ -7835,7 +7835,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 let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil, let sendAsPeerId = self.presentationInterfaceState.currentSendAsPeerId { + if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething), let sendAsPeerId = self.presentationInterfaceState.currentSendAsPeerId { attributes.append(SendAsMessageAttribute(peerId: sendAsPeerId)) } } diff --git a/submodules/TelegramUI/Sources/ChatControllerContentData.swift b/submodules/TelegramUI/Sources/ChatControllerContentData.swift index 275dafc0a9..09d0604f0a 100644 --- a/submodules/TelegramUI/Sources/ChatControllerContentData.swift +++ b/submodules/TelegramUI/Sources/ChatControllerContentData.swift @@ -866,7 +866,7 @@ extension ChatControllerImpl { } } else if let cachedChannelData = peerView.cachedData as? CachedChannelData { if let channel = peer as? TelegramChannel, channel.isMonoForum { - if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { + if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething) { currentSendAsPeerId = channel.linkedMonoforumId } else { currentSendAsPeerId = nil @@ -1365,9 +1365,16 @@ extension ChatControllerImpl { contactStatus = ChatContactStatus(canAddContact: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot) if let channel = peerView.peers[peerView.peerId] as? TelegramChannel { - if channel.flags.contains(.isCreator) || channel.adminRights != nil { + if channel.isMonoForum { + if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething) { + } else { + sendPaidMessageStars = channel.sendPaidMessageStars + } } else { - sendPaidMessageStars = channel.sendPaidMessageStars + if channel.flags.contains(.isCreator) || channel.adminRights != nil { + } else { + sendPaidMessageStars = channel.sendPaidMessageStars + } } } } @@ -1524,7 +1531,7 @@ extension ChatControllerImpl { var currentSendAsPeerId: PeerId? if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData { if peer.isMonoForum { - if let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { + if let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething) { currentSendAsPeerId = peer.linkedMonoforumId } else { currentSendAsPeerId = nil diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 62bc7a610a..ea0cd685b3 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -2999,12 +2999,20 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { displayInlineSearch = true } } - if self.chatLocation.threadId == nil, let channel = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { + if self.chatLocation.threadId == nil, let channel = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething) { if self.chatPresentationInterfaceState.search != nil { displayInlineSearch = true } } + var showNavigateButtons = true + if let _ = chatPresentationInterfaceState.inputTextPanelState.mediaRecordingState { + showNavigateButtons = false + } + if chatPresentationInterfaceState.displayHistoryFilterAsList { + showNavigateButtons = false + } + if displayInlineSearch { let peerId = self.chatPresentationInterfaceState.chatLocation.peerId @@ -3029,7 +3037,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } else { mappedContents = .empty } - } else if self.chatLocation.threadId == nil, let channel = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { + } else if self.chatLocation.threadId == nil, let channel = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething) { mappedContents = .monoforumChats(query: self.chatPresentationInterfaceState.search?.query ?? "") } else if case .peer(self.context.account.peerId) = self.chatPresentationInterfaceState.chatLocation { mappedContents = .tag(MemoryBuffer()) @@ -3037,6 +3045,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { mappedContents = .empty } + if case .empty = mappedContents { + } else { + showNavigateButtons = false + } + let context = self.context let chatLocation = self.chatLocation @@ -3148,7 +3161,12 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.controller?.alwaysShowSearchResultsAsList = false self.alwaysShowSearchResultsAsList = false self.controller?.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in - return state.updatedDisplayHistoryFilterAsList(false) + var state = state + state = state.updatedDisplayHistoryFilterAsList(false) + if let channel = state.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething) { + state = state.updatedSearch(nil) + } + return state }) } }, @@ -3270,26 +3288,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } let viewKey: PostboxViewKey = .savedMessagesIndex(peerId: peerId) - let interfaceStateKey: PostboxViewKey = .chatInterfaceState(peerId: peerId) - let accountPeerId = self.context.account.peerId - let threadListSignal: Signal = self.context.account.postbox.combinedView(keys: [viewKey, interfaceStateKey]) + let threadListSignal: Signal = self.context.account.postbox.combinedView(keys: [viewKey]) |> map { views -> EngineChatList? in guard let view = views.views[viewKey] as? MessageHistorySavedMessagesIndexView else { preconditionFailure() } - var draft: EngineChatList.Draft? - if let interfaceStateView = views.views[interfaceStateKey] as? ChatInterfaceStateView { - if let embeddedState = interfaceStateView.value, let _ = embeddedState.overrideChatTimestamp { - if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) { - if let text = opaqueState.synchronizeableInputState?.text { - draft = EngineChatList.Draft(text: text, entities: opaqueState.synchronizeableInputState?.entities ?? []) - } - } - } - } - var items: [EngineChatList.Item] = [] for item in view.items { guard let sourcePeer = item.peer else { @@ -3310,9 +3315,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { index: .chatList(ChatListIndex(pinningIndex: item.pinnedIndex.flatMap(UInt16.init), messageIndex: mappedMessageIndex)), messages: messages, readCounters: EnginePeerReadCounters( - incomingReadId: 0, outgoingReadId: 0, count: Int32(item.unreadCount), markedUnread: false), + incomingReadId: 0, outgoingReadId: 0, count: Int32(item.unreadCount), markedUnread: item.markedUnread), isMuted: false, - draft: sourceId == accountPeerId ? draft : nil, + draft: nil, threadData: nil, renderedPeer: EngineRenderedPeer(peer: EnginePeer(sourcePeer)), presence: nil, @@ -3465,6 +3470,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { transition.updateAlpha(node: self.historyNode, alpha: 1.0) transition.updateAlpha(node: self.backgroundNode, alpha: 1.0) } + + transition.updateAlpha(node: self.navigateButtons, alpha: showNavigateButtons ? 1.0 : 0.0) let listBottomInset = self.historyNode.insets.top if let previousListBottomInset = previousListBottomInset, listBottomInset != previousListBottomInset { @@ -3694,15 +3701,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.emptyNode?.isHidden = false } - var showNavigateButtons = true - if let _ = chatPresentationInterfaceState.inputTextPanelState.mediaRecordingState { - showNavigateButtons = false - } - if chatPresentationInterfaceState.displayHistoryFilterAsList { - showNavigateButtons = false - } - transition.updateAlpha(node: self.navigateButtons, alpha: showNavigateButtons ? 1.0 : 0.0) - if let openStickersDisposable = self.openStickersDisposable { if case .media = chatPresentationInterfaceState.inputMode { } else { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift index 4528e7ec16..7053e37cda 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 let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil, case .peer = chatPresentationInterfaceState.chatLocation { + if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething), 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/ChatInterfaceTitlePanelNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift index 47249ca94e..a6c73537f1 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift @@ -230,7 +230,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, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil, chatPresentationInterfaceState.search == nil { + if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething), chatPresentationInterfaceState.search == nil { let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top if case .top = topicListDisplayMode, let peerId = chatPresentationInterfaceState.chatLocation.peerId { if let currentPanel = currentPanel as? ChatTopicListTitleAccessoryPanelNode { @@ -262,7 +262,7 @@ func sidePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState return nil } - if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil, chatPresentationInterfaceState.search == nil { + if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething), chatPresentationInterfaceState.search == nil { let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top if case .side = topicListDisplayMode { return AnyComponentWithIdentity( diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index 70b66ef646..26b8afe92c 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -118,7 +118,7 @@ private enum ChatListSearchEntry: Comparable, Identifiable { hasActiveRevealControls: false, selected: false, header: nil, - enableContextActions: false, + enabledContextActions: nil, hiddenOffset: false, interaction: interaction ) diff --git a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift index 27f5f75229..dee8e42844 100644 --- a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift @@ -262,6 +262,10 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { canChangeListMode = true } + if let channel = params.interfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, params.interfaceState.chatLocation.threadId == nil, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = params.interfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething) { + canChangeListMode = false + } + let height: CGFloat if case .regular = params.metrics.widthClass { height = 49.0 diff --git a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift index d179bfdc83..8931af1240 100644 --- a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift @@ -249,7 +249,7 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable { hasActiveRevealControls: false, selected: false, header: nil, - enableContextActions: false, + enabledContextActions: nil, hiddenOffset: false, interaction: chatListNodeInteraction )