Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2025-05-23 11:17:15 +02:00
commit b0c6afdcee
34 changed files with 442 additions and 147 deletions

View File

@ -1515,11 +1515,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
switch item.index { switch item.index {
case .chatList: 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 { if let threadId = threadId {
let source: ContextContentSource let source: ContextContentSource
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .replyThread(message: ChatReplyThreadMessage( 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) )), subject: nil, botStart: nil, mode: .standard(.previewing), params: nil)
chatController.canReadHistory.set(false) chatController.canReadHistory.set(false)
source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))

View File

@ -1195,7 +1195,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
requiresPremiumForMessaging: requiresPremiumForMessaging, requiresPremiumForMessaging: requiresPremiumForMessaging,
displayAsTopicList: false, displayAsTopicList: false,
tags: [] 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): case let .messagePlaceholder(_, presentationData, searchScope):
var actionTitle: String? 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 let header = ChatListSearchItemHeader(type: .messages(location: nil), theme: presentationData.theme, strings: presentationData.strings, actionTitle: actionTitle, action: { sourceNode in
openMessagesFilter(sourceNode) 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): case let .emptyMessagesFooter(presentationData, searchScope, searchQuery):
var actionTitle: String? var actionTitle: String?
let filterTitle: String let filterTitle: String
@ -5426,7 +5426,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
requiresPremiumForMessaging: false, requiresPremiumForMessaging: false,
displayAsTopicList: false, displayAsTopicList: false,
tags: [] 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: case .media:
return nil return nil
case .links: case .links:

View File

@ -219,7 +219,7 @@ public final class ChatListShimmerNode: ASDisplayNode {
requiresPremiumForMessaging: false, requiresPremiumForMessaging: false,
displayAsTopicList: false, displayAsTopicList: false,
tags: [] 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] = [] var itemNodes: [ChatListItemNode] = []

View File

@ -453,6 +453,22 @@ private final class ChatListItemTagListComponent: Component {
} }
public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { 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 presentationData: ChatListPresentationData
let context: AccountContext let context: AccountContext
let chatListLocation: ChatListControllerLocation let chatListLocation: ChatListControllerLocation
@ -462,7 +478,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour {
let editing: Bool let editing: Bool
let hasActiveRevealControls: Bool let hasActiveRevealControls: Bool
let selected: Bool let selected: Bool
let enableContextActions: Bool let enabledContextActions: EnabledContextActions?
let hiddenOffset: Bool let hiddenOffset: Bool
let interaction: ChatListNodeInteraction 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.presentationData = presentationData
self.chatListLocation = chatListLocation self.chatListLocation = chatListLocation
self.filterData = filterData self.filterData = filterData
@ -498,7 +514,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour {
self.hasActiveRevealControls = hasActiveRevealControls self.hasActiveRevealControls = hasActiveRevealControls
self.selected = selected self.selected = selected
self.header = header self.header = header
self.enableContextActions = enableContextActions self.enabledContextActions = enabledContextActions
self.hiddenOffset = hiddenOffset self.hiddenOffset = hiddenOffset
self.interaction = interaction self.interaction = interaction
} }
@ -3493,57 +3509,74 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
} }
if item.enableContextActions { if let enabledContextActions = item.enabledContextActions {
if case .forum = item.chatListLocation { switch enabledContextActions {
if case let .chat(itemPeer) = contentPeer, case let .channel(channel) = itemPeer.peer { case .auto:
var canOpenClose = false if case .forum = item.chatListLocation {
if channel.flags.contains(.isCreator) { if case let .chat(itemPeer) = contentPeer, case let .channel(channel) = itemPeer.peer {
canOpenClose = true var canOpenClose = false
} else if channel.hasPermission(.manageTopics) { if channel.flags.contains(.isCreator) {
canOpenClose = true canOpenClose = true
} else if let threadInfo = threadInfo, threadInfo.isOwnedByMe { } else if channel.hasPermission(.manageTopics) {
canOpenClose = true canOpenClose = true
} } else if let threadInfo = threadInfo, threadInfo.isOwnedByMe {
let canDelete = channel.hasPermission(.deleteAllMessages) canOpenClose = true
var isClosed = false }
if let threadInfo { let canDelete = channel.hasPermission(.deleteAllMessages)
isClosed = threadInfo.isClosed var isClosed = false
} if let threadInfo {
if let threadInfo, threadInfo.id == 1 { isClosed = threadInfo.isClosed
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 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 { } 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 if case .psa = promoInfo {
} 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 {
peerRevealOptions = [ 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.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)
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)
] ]
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 { } else {
peerRevealOptions = [] 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 = [] peerLeftRevealOptions = []
} }
} else { case let .custom(actions):
peerRevealOptions = [] peerRevealOptions = []
peerLeftRevealOptions = [] 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 { } else {
peerRevealOptions = [] peerRevealOptions = []

View File

@ -463,7 +463,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
hasActiveRevealControls: hasActiveRevealControls, hasActiveRevealControls: hasActiveRevealControls,
selected: selected, selected: selected,
header: nil, header: nil,
enableContextActions: true, enabledContextActions: .auto,
hiddenOffset: threadInfo?.isHidden == true && !revealed, hiddenOffset: threadInfo?.isHidden == true && !revealed,
interaction: nodeInteraction interaction: nodeInteraction
), directionHint: entry.directionHint) ), directionHint: entry.directionHint)
@ -709,7 +709,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
hasActiveRevealControls: false, hasActiveRevealControls: false,
selected: false, selected: false,
header: nil, header: nil,
enableContextActions: true, enabledContextActions: .auto,
hiddenOffset: groupReferenceEntry.hiddenByDefault && !groupReferenceEntry.revealed, hiddenOffset: groupReferenceEntry.hiddenByDefault && !groupReferenceEntry.revealed,
interaction: nodeInteraction interaction: nodeInteraction
), directionHint: entry.directionHint) ), directionHint: entry.directionHint)
@ -862,7 +862,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
hasActiveRevealControls: hasActiveRevealControls, hasActiveRevealControls: hasActiveRevealControls,
selected: selected, selected: selected,
header: nil, header: nil,
enableContextActions: true, enabledContextActions: .auto,
hiddenOffset: threadInfo?.isHidden == true && !revealed, hiddenOffset: threadInfo?.isHidden == true && !revealed,
interaction: nodeInteraction interaction: nodeInteraction
), directionHint: entry.directionHint) ), directionHint: entry.directionHint)
@ -1059,7 +1059,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
hasActiveRevealControls: false, hasActiveRevealControls: false,
selected: false, selected: false,
header: nil, header: nil,
enableContextActions: true, enabledContextActions: .auto,
hiddenOffset: groupReferenceEntry.hiddenByDefault && !groupReferenceEntry.revealed, hiddenOffset: groupReferenceEntry.hiddenByDefault && !groupReferenceEntry.revealed,
interaction: nodeInteraction interaction: nodeInteraction
), directionHint: entry.directionHint) ), directionHint: entry.directionHint)

View File

@ -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 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( items.append(EngineChatList.Item(
id: .chatList(sourceId), id: .chatList(sourceId),
index: .chatList(ChatListIndex(pinningIndex: item.pinnedIndex.flatMap(UInt16.init), messageIndex: mappedMessageIndex)), index: .chatList(ChatListIndex(pinningIndex: item.pinnedIndex.flatMap(UInt16.init), messageIndex: mappedMessageIndex)),
messages: messages, messages: messages,
readCounters: nil, readCounters: readCounters,
isMuted: false, isMuted: false,
draft: sourceId == accountPeerId ? draft : nil, draft: sourceId == accountPeerId ? draft : itemDraft,
threadData: nil, threadData: nil,
renderedPeer: EngineRenderedPeer(peer: EnginePeer(sourcePeer)), renderedPeer: EngineRenderedPeer(peer: EnginePeer(sourcePeer)),
presence: nil, presence: nil,

View File

@ -8,6 +8,8 @@ final class MutableMessageHistorySavedMessagesIndexView: MutablePostboxView {
let index: MessageIndex let index: MessageIndex
let topMessage: Message? let topMessage: Message?
let unreadCount: Int let unreadCount: Int
let markedUnread: Bool
let embeddedInterfaceState: StoredPeerChatInterfaceState?
init( init(
id: Int64, id: Int64,
@ -15,7 +17,9 @@ final class MutableMessageHistorySavedMessagesIndexView: MutablePostboxView {
pinnedIndex: Int?, pinnedIndex: Int?,
index: MessageIndex, index: MessageIndex,
topMessage: Message?, topMessage: Message?,
unreadCount: Int unreadCount: Int,
markedUnread: Bool,
embeddedInterfaceState: StoredPeerChatInterfaceState?
) { ) {
self.id = id self.id = id
self.peer = peer self.peer = peer
@ -23,6 +27,8 @@ final class MutableMessageHistorySavedMessagesIndexView: MutablePostboxView {
self.index = index self.index = index
self.topMessage = topMessage self.topMessage = topMessage
self.unreadCount = unreadCount self.unreadCount = unreadCount
self.markedUnread = markedUnread
self.embeddedInterfaceState = embeddedInterfaceState
} }
} }
@ -65,13 +71,17 @@ final class MutableMessageHistorySavedMessagesIndexView: MutablePostboxView {
pinnedIndex = index pinnedIndex = index
} }
let embeddedInterfaceState = postbox.peerChatThreadInterfaceStateTable.get(PeerChatThreadId(peerId: self.peerId, threadId: item.threadId))
self.items.append(Item( self.items.append(Item(
id: item.threadId, id: item.threadId,
peer: postbox.peerTable.get(PeerId(item.threadId)), peer: postbox.peerTable.get(PeerId(item.threadId)),
pinnedIndex: pinnedIndex, pinnedIndex: pinnedIndex,
index: item.index, index: item.index,
topMessage: postbox.getMessage(item.index.id), 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 index: MessageIndex
public let topMessage: Message? public let topMessage: Message?
public let unreadCount: Int public let unreadCount: Int
public let markedUnread: Bool
public let embeddedInterfaceState: StoredPeerChatInterfaceState?
public init( public init(
id: Int64, id: Int64,
@ -132,7 +144,9 @@ public final class EngineMessageHistorySavedMessagesThread {
pinnedIndex: Int?, pinnedIndex: Int?,
index: MessageIndex, index: MessageIndex,
topMessage: Message?, topMessage: Message?,
unreadCount: Int unreadCount: Int,
markedUnread: Bool,
embeddedInterfaceState: StoredPeerChatInterfaceState?
) { ) {
self.id = id self.id = id
self.peer = peer self.peer = peer
@ -140,6 +154,8 @@ public final class EngineMessageHistorySavedMessagesThread {
self.index = index self.index = index
self.topMessage = topMessage self.topMessage = topMessage
self.unreadCount = unreadCount self.unreadCount = unreadCount
self.markedUnread = markedUnread
self.embeddedInterfaceState = embeddedInterfaceState
} }
public static func ==(lhs: Item, rhs: Item) -> Bool { public static func ==(lhs: Item, rhs: Item) -> Bool {
@ -168,6 +184,12 @@ public final class EngineMessageHistorySavedMessagesThread {
if lhs.unreadCount != rhs.unreadCount { if lhs.unreadCount != rhs.unreadCount {
return false return false
} }
if lhs.markedUnread != rhs.markedUnread {
return false
}
if lhs.embeddedInterfaceState != rhs.embeddedInterfaceState {
return false
}
return true return true
} }
@ -190,7 +212,9 @@ public final class MessageHistorySavedMessagesIndexView: PostboxView {
pinnedIndex: item.pinnedIndex, pinnedIndex: item.pinnedIndex,
index: item.index, index: item.index,
topMessage: item.topMessage, topMessage: item.topMessage,
unreadCount: item.unreadCount unreadCount: item.unreadCount,
markedUnread: item.markedUnread,
embeddedInterfaceState: item.embeddedInterfaceState
)) ))
} }
self.items = items self.items = items

View File

@ -3,20 +3,24 @@ import Foundation
public struct StoredMessageHistoryThreadInfo: Equatable, PostboxCoding { public struct StoredMessageHistoryThreadInfo: Equatable, PostboxCoding {
public struct Summary: Equatable, PostboxCoding { public struct Summary: Equatable, PostboxCoding {
public var totalUnreadCount: Int32 public var totalUnreadCount: Int32
public var isMarkedUnread: Bool
public var mutedUntil: Int32? public var mutedUntil: Int32?
public init(totalUnreadCount: Int32, mutedUntil: Int32?) { public init(totalUnreadCount: Int32, isMarkedUnread: Bool, mutedUntil: Int32?) {
self.totalUnreadCount = totalUnreadCount self.totalUnreadCount = totalUnreadCount
self.isMarkedUnread = isMarkedUnread
self.mutedUntil = mutedUntil self.mutedUntil = mutedUntil
} }
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
self.totalUnreadCount = decoder.decodeInt32ForKey("u", orElse: 0) self.totalUnreadCount = decoder.decodeInt32ForKey("u", orElse: 0)
self.mutedUntil = decoder.decodeOptionalInt32ForKey("m") self.mutedUntil = decoder.decodeOptionalInt32ForKey("m")
self.isMarkedUnread = decoder.decodeBoolForKey("mu", orElse: false)
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.totalUnreadCount, forKey: "u") encoder.encodeInt32(self.totalUnreadCount, forKey: "u")
encoder.encodeBool(self.isMarkedUnread, forKey: "mu")
if let mutedUntil = self.mutedUntil { if let mutedUntil = self.mutedUntil {
encoder.encodeInt32(mutedUntil, forKey: "m") encoder.encodeInt32(mutedUntil, forKey: "m")
} else { } else {

View File

@ -312,7 +312,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView
hasActiveRevealControls: false, hasActiveRevealControls: false,
selected: false, selected: false,
header: nil, header: nil,
enableContextActions: false, enabledContextActions: nil,
hiddenOffset: false, hiddenOffset: false,
interaction: interaction interaction: interaction
) )

View File

@ -459,7 +459,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate {
hasActiveRevealControls: false, hasActiveRevealControls: false,
selected: false, selected: false,
header: nil, header: nil,
enableContextActions: false, enabledContextActions: nil,
hiddenOffset: false, hiddenOffset: false,
interaction: interaction interaction: interaction
) )

View File

@ -5364,15 +5364,16 @@ public extension Api.functions.messages {
} }
} }
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<Api.messages.AffectedHistory>) { static func deleteSavedHistory(flags: Int32, parentPeer: Api.InputPeer?, peer: Api.InputPeer, maxId: Int32, minDate: Int32?, maxDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.AffectedHistory>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(1855459371) buffer.appendInt32(1304758367)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {parentPeer!.serialize(buffer, true)}
peer.serialize(buffer, true) peer.serialize(buffer, true)
serializeInt32(maxId, buffer: buffer, boxed: false) serializeInt32(maxId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(minDate!, 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)} 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) let reader = BufferReader(buffer)
var result: Api.messages.AffectedHistory? var result: Api.messages.AffectedHistory?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {

View File

@ -73,7 +73,7 @@ enum AccountStateMutationOperation {
case ReadOutbox(MessageId, Int32?) 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 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 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 ResetMessageTagSummary(PeerId, MessageTags, MessageId.Namespace, Int32, MessageHistoryTagNamespaceCountValidityRange)
case ReadGroupFeedInbox(PeerGroupId, MessageIndex) case ReadGroupFeedInbox(PeerGroupId, MessageIndex)
case UpdateState(AuthorizedAccountState.State) 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)) 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) { mutating func updatePeerChatUnreadMark(_ peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, value: Bool) {
self.addOperation(.UpdatePeerChatUnreadMark(peerId, namespace, value)) self.addOperation(.UpdatePeerChatUnreadMark(peerId, threadId, namespace, value))
} }
mutating func resetMessageTagSummary(_ peerId: PeerId, tag: MessageTags, namespace: MessageId.Namespace, count: Int32, range: MessageHistoryTagNamespaceCountValidityRange) { mutating func resetMessageTagSummary(_ peerId: PeerId, tag: MessageTags, namespace: MessageId.Namespace, count: Int32, range: MessageHistoryTagNamespaceCountValidityRange) {

View File

@ -67,6 +67,7 @@ public struct MessageHistoryThreadData: Codable, Equatable {
case isClosed case isClosed
case isHidden case isHidden
case notificationSettings case notificationSettings
case isMarkedUnread
} }
public var creationDate: Int32 public var creationDate: Int32
@ -74,7 +75,8 @@ public struct MessageHistoryThreadData: Codable, Equatable {
public var author: PeerId public var author: PeerId
public var info: EngineMessageHistoryThread.Info public var info: EngineMessageHistoryThread.Info
public var incomingUnreadCount: Int32 public var incomingUnreadCount: Int32
public var maxIncomingReadId: Int32 public var isMarkedUnread: Bool
public var maxIncomingReadId: Int32
public var maxKnownMessageId: Int32 public var maxKnownMessageId: Int32
public var maxOutgoingReadId: Int32 public var maxOutgoingReadId: Int32
public var isClosed: Bool public var isClosed: Bool
@ -87,6 +89,7 @@ public struct MessageHistoryThreadData: Codable, Equatable {
author: PeerId, author: PeerId,
info: EngineMessageHistoryThread.Info, info: EngineMessageHistoryThread.Info,
incomingUnreadCount: Int32, incomingUnreadCount: Int32,
isMarkedUnread: Bool,
maxIncomingReadId: Int32, maxIncomingReadId: Int32,
maxKnownMessageId: Int32, maxKnownMessageId: Int32,
maxOutgoingReadId: Int32, maxOutgoingReadId: Int32,
@ -99,6 +102,7 @@ public struct MessageHistoryThreadData: Codable, Equatable {
self.author = author self.author = author
self.info = info self.info = info
self.incomingUnreadCount = incomingUnreadCount self.incomingUnreadCount = incomingUnreadCount
self.isMarkedUnread = isMarkedUnread
self.maxIncomingReadId = maxIncomingReadId self.maxIncomingReadId = maxIncomingReadId
self.maxKnownMessageId = maxKnownMessageId self.maxKnownMessageId = maxKnownMessageId
self.maxOutgoingReadId = maxOutgoingReadId self.maxOutgoingReadId = maxOutgoingReadId
@ -115,6 +119,7 @@ public struct MessageHistoryThreadData: Codable, Equatable {
self.author = try container.decode(PeerId.self, forKey: .author) self.author = try container.decode(PeerId.self, forKey: .author)
self.info = try container.decode(EngineMessageHistoryThread.Info.self, forKey: .info) self.info = try container.decode(EngineMessageHistoryThread.Info.self, forKey: .info)
self.incomingUnreadCount = try container.decode(Int32.self, forKey: .incomingUnreadCount) 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.maxIncomingReadId = try container.decode(Int32.self, forKey: .maxIncomingReadId)
self.maxKnownMessageId = try container.decode(Int32.self, forKey: .maxKnownMessageId) self.maxKnownMessageId = try container.decode(Int32.self, forKey: .maxKnownMessageId)
self.maxOutgoingReadId = try container.decode(Int32.self, forKey: .maxOutgoingReadId) 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.author, forKey: .author)
try container.encode(self.info, forKey: .info) try container.encode(self.info, forKey: .info)
try container.encode(self.incomingUnreadCount, forKey: .incomingUnreadCount) 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.maxIncomingReadId, forKey: .maxIncomingReadId)
try container.encode(self.maxKnownMessageId, forKey: .maxKnownMessageId) try container.encode(self.maxKnownMessageId, forKey: .maxKnownMessageId)
try container.encode(self.maxOutgoingReadId, forKey: .maxOutgoingReadId) try container.encode(self.maxOutgoingReadId, forKey: .maxOutgoingReadId)
@ -154,6 +160,7 @@ extension StoredMessageHistoryThreadInfo {
} }
self.init(data: entry, summary: Summary( self.init(data: entry, summary: Summary(
totalUnreadCount: data.incomingUnreadCount, totalUnreadCount: data.incomingUnreadCount,
isMarkedUnread: data.isMarkedUnread,
mutedUntil: mutedUntil mutedUntil: mutedUntil
)) ))
} }
@ -654,6 +661,7 @@ public func _internal_fillSavedMessageHistory(accountPeerId: PeerId, postbox: Po
author: accountPeerId, author: accountPeerId,
info: EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0), info: EngineMessageHistoryThread.Info(title: "", icon: nil, iconColor: 0),
incomingUnreadCount: 0, incomingUnreadCount: 0,
isMarkedUnread: false,
maxIncomingReadId: 0, maxIncomingReadId: 0,
maxKnownMessageId: 0, maxKnownMessageId: 0,
maxOutgoingReadId: 0, maxOutgoingReadId: 0,
@ -771,6 +779,7 @@ func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Post
iconColor: 0 iconColor: 0
), ),
incomingUnreadCount: 0, incomingUnreadCount: 0,
isMarkedUnread: false,
maxIncomingReadId: 0, maxIncomingReadId: 0,
maxKnownMessageId: topMessage, maxKnownMessageId: topMessage,
maxOutgoingReadId: 0, maxOutgoingReadId: 0,
@ -812,7 +821,8 @@ func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Post
index: topicIndex, index: topicIndex,
threadPeer: threadPeer 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( let data = MessageHistoryThreadData(
creationDate: 0, creationDate: 0,
isOwnedByMe: true, isOwnedByMe: true,
@ -823,6 +833,7 @@ func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Post
iconColor: 0 iconColor: 0
), ),
incomingUnreadCount: unreadCount, incomingUnreadCount: unreadCount,
isMarkedUnread: isMarkedUnread,
maxIncomingReadId: readInboxMaxId, maxIncomingReadId: readInboxMaxId,
maxKnownMessageId: topMessage, maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId, maxOutgoingReadId: readOutboxMaxId,
@ -971,6 +982,7 @@ func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Post
iconColor: iconColor iconColor: iconColor
), ),
incomingUnreadCount: unreadCount, incomingUnreadCount: unreadCount,
isMarkedUnread: false,
maxIncomingReadId: readInboxMaxId, maxIncomingReadId: readInboxMaxId,
maxKnownMessageId: topMessage, maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId, maxOutgoingReadId: readOutboxMaxId,

View File

@ -1224,11 +1224,11 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
switch peer { switch peer {
case let .dialogPeer(peer): case let .dialogPeer(peer):
let peerId = peer.peerId let peerId = peer.peerId
//TODO:release
if let savedPeerId { if let savedPeerId {
updatedState.updatePeerChatUnreadMark(peerId, threadId: savedPeerId.peerId.toInt64(), namespace: Namespaces.Message.Cloud, value: (flags & (1 << 0)) != 0)
let _ = savedPeerId let _ = savedPeerId
} else { } 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: case .dialogPeerFolder:
break break
@ -2080,6 +2080,7 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM
iconColor: iconColor iconColor: iconColor
), ),
incomingUnreadCount: unreadCount, incomingUnreadCount: unreadCount,
isMarkedUnread: false,
maxIncomingReadId: readInboxMaxId, maxIncomingReadId: readInboxMaxId,
maxKnownMessageId: topMessage, maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId, maxOutgoingReadId: readOutboxMaxId,
@ -2098,7 +2099,7 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM
} }
case let .savedDialog(savedDialog): case let .savedDialog(savedDialog):
switch savedDialog { switch savedDialog {
case let .monoForumDialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _): case let .monoForumDialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _):
state.operations.append(.ResetForumTopic( state.operations.append(.ResetForumTopic(
topicId: PeerAndBoundThreadId(peerId: peerId, threadId: peer.peerId.toInt64()), topicId: PeerAndBoundThreadId(peerId: peerId, threadId: peer.peerId.toInt64()),
@ -2113,6 +2114,7 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM
iconColor: 0 iconColor: 0
), ),
incomingUnreadCount: unreadCount, incomingUnreadCount: unreadCount,
isMarkedUnread: (flags & (1 << 3)) != 0,
maxIncomingReadId: readInboxMaxId, maxIncomingReadId: readInboxMaxId,
maxKnownMessageId: topMessage, maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId, maxOutgoingReadId: readOutboxMaxId,
@ -2229,6 +2231,7 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM
iconColor: iconColor iconColor: iconColor
), ),
incomingUnreadCount: unreadCount, incomingUnreadCount: unreadCount,
isMarkedUnread: false,
maxIncomingReadId: readInboxMaxId, maxIncomingReadId: readInboxMaxId,
maxKnownMessageId: topMessage, maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId, maxOutgoingReadId: readOutboxMaxId,
@ -2247,7 +2250,7 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM
} }
case let .savedDialog(savedDialog): case let .savedDialog(savedDialog):
switch savedDialog { switch savedDialog {
case let .monoForumDialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _): case let .monoForumDialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _):
let data = MessageHistoryThreadData( let data = MessageHistoryThreadData(
creationDate: 0, creationDate: 0,
isOwnedByMe: true, isOwnedByMe: true,
@ -2258,6 +2261,7 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM
iconColor: 0 iconColor: 0
), ),
incomingUnreadCount: unreadCount, incomingUnreadCount: unreadCount,
isMarkedUnread: (flags & (1 << 3)) != 0,
maxIncomingReadId: readInboxMaxId, maxIncomingReadId: readInboxMaxId,
maxKnownMessageId: topMessage, maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId, maxOutgoingReadId: readOutboxMaxId,
@ -2381,6 +2385,7 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM
iconColor: iconColor iconColor: iconColor
), ),
incomingUnreadCount: unreadCount, incomingUnreadCount: unreadCount,
isMarkedUnread: false,
maxIncomingReadId: readInboxMaxId, maxIncomingReadId: readInboxMaxId,
maxKnownMessageId: topMessage, maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId, maxOutgoingReadId: readOutboxMaxId,
@ -2397,7 +2402,7 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM
} }
case let .savedDialog(savedDialog): case let .savedDialog(savedDialog):
switch 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( fetchedChatList.threadInfos[PeerAndBoundThreadId(peerId: peerId, threadId: peer.peerId.toInt64())] = StoreMessageHistoryThreadData(
data: MessageHistoryThreadData( data: MessageHistoryThreadData(
@ -2410,6 +2415,7 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM
iconColor: 0 iconColor: 0
), ),
incomingUnreadCount: unreadCount, incomingUnreadCount: unreadCount,
isMarkedUnread: (flags & (1 << 3)) != 0,
maxIncomingReadId: readInboxMaxId, maxIncomingReadId: readInboxMaxId,
maxKnownMessageId: topMessage, maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId, maxOutgoingReadId: readOutboxMaxId,
@ -4360,8 +4366,17 @@ func replayFinalState(
transaction.setNeedsIncomingReadStateSynchronization(peerId) transaction.setNeedsIncomingReadStateSynchronization(peerId)
invalidateGroupStats.insert(groupId) invalidateGroupStats.insert(groupId)
} }
case let .UpdatePeerChatUnreadMark(peerId, namespace, value): case let .UpdatePeerChatUnreadMark(peerId, threadId, namespace, value):
transaction.applyMarkUnread(peerId: peerId, namespace: namespace, value: value, interactive: false) 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): 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) transaction.replaceMessageTagSummary(peerId: peerId, threadId: nil, tagMask: tag, namespace: namespace, customTag: nil, count: count, maxId: range.maxId)
if count == 0 { if count == 0 {

View File

@ -453,7 +453,7 @@ private func _internal_clearHistory(transaction: Transaction, postbox: Postbox,
flags |= 1 << 3 flags |= 1 << 3
updatedMaxId = 0 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 |> map { result -> Api.messages.AffectedHistory? in
return result return result
} }
@ -490,15 +490,62 @@ private func _internal_clearHistory(transaction: Transaction, postbox: Postbox,
return .complete() return .complete()
} else { } else {
if let threadId = operation.threadId { if let threadId = operation.threadId {
return network.request(Api.functions.channels.deleteTopicHistory(channel: inputChannel, topMsgId: Int32(clamping: threadId))) if peer.isMonoForum {
|> map(Optional.init) guard let inputPeer = apiInputPeer(peer) else {
|> `catch` { _ -> Signal<Api.messages.AffectedHistory?, NoError> in return .complete()
return .single(nil) }
} guard let inputSubPeer = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer) else {
|> mapToSignal { result -> Signal<Void, NoError> in return .complete()
if let _ = result { }
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<Api.messages.AffectedHistory?, Bool> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Void, Bool> 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<Void, NoError> in
return .complete()
}
} else {
return network.request(Api.functions.channels.deleteTopicHistory(channel: inputChannel, topMsgId: Int32(clamping: threadId)))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.AffectedHistory?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Void, NoError> in
if let _ = result {
}
return .complete()
} }
return .complete()
} }
} else { } else {
var flags: Int32 = 0 var flags: Int32 = 0

View File

@ -190,7 +190,7 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = {
}, },
automaticThreadIndexInfo: { peerId, _ in automaticThreadIndexInfo: { peerId, _ in
if peerId.namespace == Namespaces.Peer.CloudUser { 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 { } else {
return nil return nil
} }

View File

@ -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) { func _internal_markForumThreadAsReadInteractively(transaction: Transaction, network: Network, viewTracker: AccountViewTracker, peerId: PeerId, threadId: Int64) {
guard let peer = transaction.getPeer(peerId) else { guard let peer = transaction.getPeer(peerId) else {
return return
@ -130,6 +182,7 @@ func _internal_markForumThreadAsReadInteractively(transaction: Transaction, netw
} }
if data.incomingUnreadCount != 0 { if data.incomingUnreadCount != 0 {
data.incomingUnreadCount = 0 data.incomingUnreadCount = 0
data.isMarkedUnread = false
data.maxIncomingReadId = max(messageIndex.id.id, data.maxIncomingReadId) data.maxIncomingReadId = max(messageIndex.id.id, data.maxIncomingReadId)
data.maxKnownMessageId = max(data.maxKnownMessageId, messageIndex.id.id) data.maxKnownMessageId = max(data.maxKnownMessageId, messageIndex.id.id)
@ -169,6 +222,7 @@ func _internal_togglePeerUnreadMarkInteractively(transaction: Transaction, netwo
} }
if data.incomingUnreadCount != 0 { if data.incomingUnreadCount != 0 {
data.incomingUnreadCount = 0 data.incomingUnreadCount = 0
data.isMarkedUnread = false
data.maxIncomingReadId = max(messageIndex.id.id, data.maxIncomingReadId) data.maxIncomingReadId = max(messageIndex.id.id, data.maxIncomingReadId)
data.maxKnownMessageId = max(data.maxKnownMessageId, messageIndex.id.id) data.maxKnownMessageId = max(data.maxKnownMessageId, messageIndex.id.id)

View File

@ -740,6 +740,13 @@ public extension TelegramEngine {
|> ignoreValues |> ignoreValues
} }
public func togglePeerUnreadMarkInteractively(peerId: EnginePeer.Id, threadId: Int64, setToValue: Bool?) -> Signal<Never, NoError> {
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<Never, NoError> { public func markForumThreadAsRead(peerId: EnginePeer.Id, threadId: Int64) -> Signal<Never, NoError> {
return self.account.postbox.transaction { transaction -> Void in return self.account.postbox.transaction { transaction -> Void in
_internal_markForumThreadAsReadInteractively(transaction: transaction, network: self.account.network, viewTracker: self.account.viewTracker, peerId: peerId, threadId: threadId) _internal_markForumThreadAsReadInteractively(transaction: transaction, network: self.account.network, viewTracker: self.account.viewTracker, peerId: peerId, threadId: threadId)

View File

@ -277,6 +277,9 @@ public final class ChatInlineSearchResultsListComponent: Component {
return self.isReadyPromise.get() return self.isReadyPromise.get()
} }
private var hintDeletedChats = Set<EnginePeer.Id>()
private var hintAnimateListTransition: Bool = false
override public init(frame: CGRect) { override public init(frame: CGRect) {
self.listNode = ListView() 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? { override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let result = super.hitTest(point, with: event) else { guard let result = super.hitTest(point, with: event) else {
return nil return nil
@ -655,9 +686,33 @@ public final class ChatInlineSearchResultsListComponent: Component {
let contentsId = self.nextContentsId let contentsId = self.nextContentsId
self.nextContentsId += 1 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( self.contentsState = ContentsState(
id: contentsId, id: contentsId,
contentId: .search(query), contentId: contentId,
entries: entries, entries: entries,
messages: messages, messages: messages,
hasEarlier: false, //!(result?.completed ?? true), hasEarlier: false, //!(result?.completed ?? true),
@ -783,9 +838,17 @@ public final class ChatInlineSearchResultsListComponent: Component {
}, },
setPeerThreadMuted: { _, _, _ in 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 setPeerThreadStopped: { _, _, _ in
}, },
@ -795,7 +858,11 @@ public final class ChatInlineSearchResultsListComponent: Component {
}, },
updatePeerGrouping: { _, _ in updatePeerGrouping: { _, _ in
}, },
togglePeerMarkedUnread: { _, _ in togglePeerMarkedUnread: { [weak self] peerId, _ in
guard let self else {
return
}
self.performToggleUnreadAction(peerId: peerId, threadId: nil)
}, },
toggleArchivedFolderHiddenByDefault: { toggleArchivedFolderHiddenByDefault: {
}, },
@ -960,7 +1027,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
return ChatListItem( return ChatListItem(
presentationData: chatListPresentationData, presentationData: chatListPresentationData,
context: component.context, 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, filterData: nil,
index: .forum( index: .forum(
pinnedIndex: .none, pinnedIndex: .none,
@ -997,7 +1064,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
hasActiveRevealControls: false, hasActiveRevealControls: false,
selected: false, selected: false,
header: displayMessagesHeader ? ChatListSearchItemHeader(type: .messages(location: nil), theme: listPresentationData.theme, strings: listPresentationData.strings) : nil, header: displayMessagesHeader ? ChatListSearchItemHeader(type: .messages(location: nil), theme: listPresentationData.theme, strings: listPresentationData.strings) : nil,
enableContextActions: false, enabledContextActions: nil,
hiddenOffset: false, hiddenOffset: false,
interaction: chatListNodeInteraction interaction: chatListNodeInteraction
) )
@ -1012,12 +1079,12 @@ public final class ChatInlineSearchResultsListComponent: Component {
messages: item.messages, messages: item.messages,
peer: item.renderedPeer, peer: item.renderedPeer,
threadInfo: nil, threadInfo: nil,
combinedReadState: nil, combinedReadState: item.readCounters,
isRemovedFromTotalUnreadCount: false, isRemovedFromTotalUnreadCount: false,
presence: nil, presence: nil,
hasUnseenMentions: false, hasUnseenMentions: false,
hasUnseenReactions: false, hasUnseenReactions: false,
draftState: nil, draftState: item.draft.flatMap(ChatListItemContent.DraftState.init(draft:)),
mediaDraftContentType: nil, mediaDraftContentType: nil,
inputActivities: nil, inputActivities: nil,
promoInfo: nil, promoInfo: nil,
@ -1036,7 +1103,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
hasActiveRevealControls: false, hasActiveRevealControls: false,
selected: false, selected: false,
header: nil, header: nil,
enableContextActions: false, enabledContextActions: .custom([.toggleUnread, .delete]),
hiddenOffset: false, hiddenOffset: false,
interaction: chatListNodeInteraction interaction: chatListNodeInteraction
) )
@ -1044,6 +1111,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
} }
var scrollToItem: ListViewScrollToItem? var scrollToItem: ListViewScrollToItem?
var listTransactionOptions: ListViewDeleteAndInsertOptions = [.Synchronous, .LowLatency, .PreferSynchronousDrawing, .PreferSynchronousResourceLoading]
if previousContentsState?.contentId != contentsState.contentId && !contentsState.entries.isEmpty { if previousContentsState?.contentId != contentsState.contentId && !contentsState.entries.isEmpty {
scrollToItem = ListViewScrollToItem( scrollToItem = ListViewScrollToItem(
index: 0, 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( self.listNode.transaction(
deleteIndices: deleteIndices.map { index in deleteIndices: deleteIndices.map { index in
return ListViewDeleteItem(index: index, directionHint: nil) return ListViewDeleteItem(index: index, directionHint: nil)
@ -1075,7 +1148,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
directionHint: nil directionHint: nil
) )
}, },
options: [.Synchronous, .LowLatency, .PreferSynchronousDrawing, .PreferSynchronousResourceLoading], options: listTransactionOptions,
scrollToItem: scrollToItem, scrollToItem: scrollToItem,
updateSizeAndInsets: nil, updateSizeAndInsets: nil,
updateOpaqueState: contentsState.id updateOpaqueState: contentsState.id

View File

@ -1713,7 +1713,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
} }
} else if item.message.id.peerId.isRepliesOrVerificationCodes { } else if item.message.id.peerId.isRepliesOrVerificationCodes {
needsShareButton = false 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 { if incoming {
needsShareButton = true needsShareButton = true
} }

View File

@ -327,7 +327,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
} }
} else { } else {
if channel.isMonoForum { 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 headerSeparableThreadId = content.firstMessage.threadId
if let threadId = content.firstMessage.threadId, let peer = content.firstMessage.peers[EnginePeer.Id(threadId)] { if let threadId = content.firstMessage.threadId, let peer = content.firstMessage.peers[EnginePeer.Id(threadId)] {

View File

@ -138,10 +138,15 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
authorString = EnginePeer(author).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) authorString = EnginePeer(author).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)
} }
if let threadData = item.threadData { if case let .channel(channel) = peer, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = firstMessage.peers[linkedMonoforumId] {
title = "\(authorString)\(threadData.info.title)" //TODO:localize
title = authorString + "@" + EnginePeer(mainChannel).displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) + " Messages"
} else { } 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 { } else {
title = peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder) title = peer.displayTitle(strings: item.strings, displayOrder: item.nameDisplayOrder)

View File

@ -244,7 +244,7 @@ public final class LoadingOverlayNode: ASDisplayNode {
requiresPremiumForMessaging: false, requiresPremiumForMessaging: false,
displayAsTopicList: false, displayAsTopicList: false,
tags: [] 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] = [] var itemNodes: [ChatListItemNode] = []
@ -616,7 +616,7 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod
hasActiveRevealControls: false, hasActiveRevealControls: false,
selected: false, selected: false,
header: nil, header: nil,
enableContextActions: false, enabledContextActions: nil,
hiddenOffset: false, hiddenOffset: false,
interaction: chatListNodeInteraction interaction: chatListNodeInteraction
) )

View File

@ -264,7 +264,7 @@ final class GreetingMessageListItemComponent: Component {
hasActiveRevealControls: false, hasActiveRevealControls: false,
selected: false, selected: false,
header: nil, header: nil,
enableContextActions: false, enabledContextActions: nil,
hiddenOffset: false, hiddenOffset: false,
interaction: chatListNodeInteraction interaction: chatListNodeInteraction
) )

View File

@ -294,7 +294,7 @@ final class QuickReplySetupScreenComponent: Component {
hasActiveRevealControls: false, hasActiveRevealControls: false,
selected: isSelected, selected: isSelected,
header: nil, header: nil,
enableContextActions: true, enabledContextActions: .auto,
hiddenOffset: false, hiddenOffset: false,
interaction: chatListNodeInteraction interaction: chatListNodeInteraction
) )

View File

@ -954,7 +954,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate
hasActiveRevealControls: false, hasActiveRevealControls: false,
selected: false, selected: false,
header: nil, header: nil,
enableContextActions: false, enabledContextActions: nil,
hiddenOffset: false, hiddenOffset: false,
interaction: interaction interaction: interaction
) )

View File

@ -4761,7 +4761,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let self else { guard let self else {
return 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 return direction ? .right : .left
} }
self.updateChatLocationThread(threadId: threadId, animationDirection: animationDirection ?? defaultDirection) 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 { if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum {
attributes.removeAll(where: { $0 is SendAsMessageAttribute }) 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)) attributes.append(SendAsMessageAttribute(peerId: sendAsPeerId))
} }
} }

View File

@ -866,7 +866,7 @@ extension ChatControllerImpl {
} }
} else if let cachedChannelData = peerView.cachedData as? CachedChannelData { } else if let cachedChannelData = peerView.cachedData as? CachedChannelData {
if let channel = peer as? TelegramChannel, channel.isMonoForum { 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 currentSendAsPeerId = channel.linkedMonoforumId
} else { } else {
currentSendAsPeerId = nil currentSendAsPeerId = nil
@ -1365,9 +1365,16 @@ extension ChatControllerImpl {
contactStatus = ChatContactStatus(canAddContact: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot) contactStatus = ChatContactStatus(canAddContact: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
if let channel = peerView.peers[peerView.peerId] as? TelegramChannel { 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 { } 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? var currentSendAsPeerId: PeerId?
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData { if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData {
if peer.isMonoForum { 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 currentSendAsPeerId = peer.linkedMonoforumId
} else { } else {
currentSendAsPeerId = nil currentSendAsPeerId = nil

View File

@ -2999,12 +2999,20 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
displayInlineSearch = true 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 { if self.chatPresentationInterfaceState.search != nil {
displayInlineSearch = true displayInlineSearch = true
} }
} }
var showNavigateButtons = true
if let _ = chatPresentationInterfaceState.inputTextPanelState.mediaRecordingState {
showNavigateButtons = false
}
if chatPresentationInterfaceState.displayHistoryFilterAsList {
showNavigateButtons = false
}
if displayInlineSearch { if displayInlineSearch {
let peerId = self.chatPresentationInterfaceState.chatLocation.peerId let peerId = self.chatPresentationInterfaceState.chatLocation.peerId
@ -3029,7 +3037,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
} else { } else {
mappedContents = .empty 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 ?? "") mappedContents = .monoforumChats(query: self.chatPresentationInterfaceState.search?.query ?? "")
} else if case .peer(self.context.account.peerId) = self.chatPresentationInterfaceState.chatLocation { } else if case .peer(self.context.account.peerId) = self.chatPresentationInterfaceState.chatLocation {
mappedContents = .tag(MemoryBuffer()) mappedContents = .tag(MemoryBuffer())
@ -3037,6 +3045,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
mappedContents = .empty mappedContents = .empty
} }
if case .empty = mappedContents {
} else {
showNavigateButtons = false
}
let context = self.context let context = self.context
let chatLocation = self.chatLocation let chatLocation = self.chatLocation
@ -3148,7 +3161,12 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.controller?.alwaysShowSearchResultsAsList = false self.controller?.alwaysShowSearchResultsAsList = false
self.alwaysShowSearchResultsAsList = false self.alwaysShowSearchResultsAsList = false
self.controller?.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in 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 viewKey: PostboxViewKey = .savedMessagesIndex(peerId: peerId)
let interfaceStateKey: PostboxViewKey = .chatInterfaceState(peerId: peerId)
let accountPeerId = self.context.account.peerId let threadListSignal: Signal<EngineChatList?, NoError> = self.context.account.postbox.combinedView(keys: [viewKey])
let threadListSignal: Signal<EngineChatList?, NoError> = self.context.account.postbox.combinedView(keys: [viewKey, interfaceStateKey])
|> map { views -> EngineChatList? in |> map { views -> EngineChatList? in
guard let view = views.views[viewKey] as? MessageHistorySavedMessagesIndexView else { guard let view = views.views[viewKey] as? MessageHistorySavedMessagesIndexView else {
preconditionFailure() 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] = [] var items: [EngineChatList.Item] = []
for item in view.items { for item in view.items {
guard let sourcePeer = item.peer else { 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)), index: .chatList(ChatListIndex(pinningIndex: item.pinnedIndex.flatMap(UInt16.init), messageIndex: mappedMessageIndex)),
messages: messages, messages: messages,
readCounters: EnginePeerReadCounters( 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, isMuted: false,
draft: sourceId == accountPeerId ? draft : nil, draft: nil,
threadData: nil, threadData: nil,
renderedPeer: EngineRenderedPeer(peer: EnginePeer(sourcePeer)), renderedPeer: EngineRenderedPeer(peer: EnginePeer(sourcePeer)),
presence: nil, presence: nil,
@ -3465,6 +3470,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
transition.updateAlpha(node: self.historyNode, alpha: 1.0) transition.updateAlpha(node: self.historyNode, alpha: 1.0)
transition.updateAlpha(node: self.backgroundNode, 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 let listBottomInset = self.historyNode.insets.top
if let previousListBottomInset = previousListBottomInset, listBottomInset != previousListBottomInset { if let previousListBottomInset = previousListBottomInset, listBottomInset != previousListBottomInset {
@ -3694,15 +3701,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.emptyNode?.isHidden = false 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 let openStickersDisposable = self.openStickersDisposable {
if case .media = chatPresentationInterfaceState.inputMode { if case .media = chatPresentationInterfaceState.inputMode {
} else { } else {

View File

@ -230,7 +230,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
} }
if channel.flags.contains(.isMonoforum) { 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 { if chatPresentationInterfaceState.interfaceState.replyMessageSubject == nil {
displayInputTextPanel = false displayInputTextPanel = false
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) { if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {

View File

@ -230,7 +230,7 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
} }
func titleTopicsPanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatTitleAccessoryPanelNode?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, force: Bool) -> ChatTopicListTitleAccessoryPanelNode? { 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 let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top
if case .top = topicListDisplayMode, let peerId = chatPresentationInterfaceState.chatLocation.peerId { if case .top = topicListDisplayMode, let peerId = chatPresentationInterfaceState.chatLocation.peerId {
if let currentPanel = currentPanel as? ChatTopicListTitleAccessoryPanelNode { if let currentPanel = currentPanel as? ChatTopicListTitleAccessoryPanelNode {
@ -262,7 +262,7 @@ func sidePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState
return nil 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 let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top
if case .side = topicListDisplayMode { if case .side = topicListDisplayMode {
return AnyComponentWithIdentity( return AnyComponentWithIdentity(

View File

@ -118,7 +118,7 @@ private enum ChatListSearchEntry: Comparable, Identifiable {
hasActiveRevealControls: false, hasActiveRevealControls: false,
selected: false, selected: false,
header: nil, header: nil,
enableContextActions: false, enabledContextActions: nil,
hiddenOffset: false, hiddenOffset: false,
interaction: interaction interaction: interaction
) )

View File

@ -262,6 +262,10 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
canChangeListMode = true 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 let height: CGFloat
if case .regular = params.metrics.widthClass { if case .regular = params.metrics.widthClass {
height = 49.0 height = 49.0

View File

@ -249,7 +249,7 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable {
hasActiveRevealControls: false, hasActiveRevealControls: false,
selected: false, selected: false,
header: nil, header: nil,
enableContextActions: false, enabledContextActions: nil,
hiddenOffset: false, hiddenOffset: false,
interaction: chatListNodeInteraction interaction: chatListNodeInteraction
) )