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

This commit is contained in:
Mikhail Filimonov 2025-08-15 13:04:42 +01:00
commit 2d04763fae
17 changed files with 403 additions and 64 deletions

View File

@ -1042,7 +1042,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
).get(), let linkedForumId {
subject = .botForumThread(forumId: linkedForumId, threadId: threadIdValue)
}
subject = nil
} else if case let .user(user) = peer {
if case let .known(linkedForumId) = await self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.LinkedBotForumPeerId(id: user.id)
).get(), let linkedForumId {
subject = .botForumThread(forumId: linkedForumId, threadId: EngineMessage.newTopicThreadId)
}
}
var forumSourcePeer: Signal<EnginePeer?, NoError> = .single(nil)

View File

@ -651,6 +651,8 @@ public struct MessageGroupInfo: Equatable {
}
public final class Message {
public static let newTopicThreadId: Int64 = Int64(Int32.max - 1)
public final class AssociatedThreadInfo: Equatable {
public let title: String
public let icon: Int64?

View File

@ -75,7 +75,7 @@ final class MessageCustomTagHoleIndexTable: Table {
if !self.metadataTable.isPeerCustomTagInitialized(peerId: peerId, threadId: threadId, tag: tag) {
self.metadataTable.setPeerCustomTagInitialized(peerId: peerId, threadId: threadId, tag: tag)
if let namespaces = self.seedConfiguration.messageThreadHoles[peerId.namespace] {
if let namespaces = self.seedConfiguration.messageThreadHoles(peerId.namespace, threadId) {
for namespace in namespaces {
var operations: [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]] = [:]
self.addInternal(peerId: peerId, threadId: threadId, tag: tag, tagValue: tagValue, namespace: namespace, range: 1 ... (Int32.max - 1), operations: &operations)

View File

@ -79,7 +79,7 @@ final class MessageCustomTagWithTagHoleIndexTable: Table {
if !self.metadataTable.isPeerCustomTagInitialized(peerId: peerId, threadId: threadId, tag: tag, regularTag: regularTag) {
self.metadataTable.setPeerCustomTagInitialized(peerId: peerId, threadId: threadId, tag: tag, regularTag: regularTag)
if let namespaces = self.seedConfiguration.messageThreadHoles[peerId.namespace] {
if let namespaces = self.seedConfiguration.messageThreadHoles(peerId.namespace, threadId) {
for namespace in namespaces {
var operations: [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]] = [:]
self.addInternal(peerId: peerId, threadId: threadId, tag: tag, tagValue: tagValue, regularTag: regularTag, namespace: namespace, range: 1 ... (Int32.max - 1), operations: &operations)

View File

@ -113,7 +113,7 @@ final class MessageHistoryThreadHoleIndexTable: Table {
postboxLog("MessageHistoryThreadHoleIndexTable: Initializing \(peerId) \(threadId)")
self.metadataTable.setIsThreadHoleIndexInitialized(peerId: peerId, threadId: threadId)
if let messageNamespaces = self.seedConfiguration.messageThreadHoles[peerId.namespace] {
if let messageNamespaces = self.seedConfiguration.messageThreadHoles(peerId.namespace, threadId) {
for namespace in messageNamespaces {
var operations: [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]] = [:]
self.add(peerId: peerId, threadId: threadId, namespace: namespace, space: .everywhere, range: 1 ... (Int32.max - 1), operations: &operations)

View File

@ -60,7 +60,7 @@ public final class SeedConfiguration {
public let initializeChatListWithHole: (topLevel: ChatListHole?, groups: ChatListHole?)
public let messageHoles: [PeerId.Namespace: [MessageId.Namespace: Set<MessageTags>]]
public let upgradedMessageHoles: [PeerId.Namespace: [MessageId.Namespace: Set<MessageTags>]]
public let messageThreadHoles: [PeerId.Namespace: [MessageId.Namespace]]
public let messageThreadHoles: (PeerId.Namespace, Int64?) -> [MessageId.Namespace]?
public let messageTagsWithSummary: MessageTags
public let messageTagsWithThreadSummary: MessageTags
public let existingGlobalMessageTags: GlobalMessageTags
@ -91,7 +91,7 @@ public final class SeedConfiguration {
),
messageHoles: [PeerId.Namespace: [MessageId.Namespace: Set<MessageTags>]],
upgradedMessageHoles: [PeerId.Namespace: [MessageId.Namespace: Set<MessageTags>]],
messageThreadHoles: [PeerId.Namespace: [MessageId.Namespace]],
messageThreadHoles: @escaping (PeerId.Namespace, Int64?) -> [MessageId.Namespace]?,
existingMessageTags: MessageTags,
messageTagsWithSummary: MessageTags,
messageTagsWithThreadSummary: MessageTags,

View File

@ -202,7 +202,11 @@ public enum CreateForumChannelTopicError {
}
func _internal_createForumChannelTopic(account: Account, peerId: PeerId, title: String, iconColor: Int32, iconFileId: Int64?) -> Signal<Int64, CreateForumChannelTopicError> {
return account.postbox.transaction { transaction -> Peer? in
return _internal_createForumChannelTopic(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, title: title, iconColor: iconColor, iconFileId: iconFileId)
}
func _internal_createForumChannelTopic(postbox: Postbox, network: Network, stateManager: AccountStateManager, accountPeerId: PeerId, peerId: PeerId, title: String, iconColor: Int32, iconFileId: Int64?) -> Signal<Int64, CreateForumChannelTopicError> {
return postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
}
|> castError(CreateForumChannelTopicError.self)
@ -218,7 +222,7 @@ func _internal_createForumChannelTopic(account: Account, peerId: PeerId, title:
flags |= (1 << 3)
}
flags |= (1 << 0)
return account.network.request(Api.functions.channels.createForumTopic(
return network.request(Api.functions.channels.createForumTopic(
flags: flags,
channel: inputChannel,
title: title,
@ -231,14 +235,14 @@ func _internal_createForumChannelTopic(account: Account, peerId: PeerId, title:
return .generic
}
|> mapToSignal { result -> Signal<Int64, CreateForumChannelTopicError> in
account.stateManager.addUpdates(result)
stateManager.addUpdates(result)
var topicId: Int64?
topicId = nil
for update in result.allUpdates {
switch update {
case let .updateNewChannelMessage(message, _, _):
if let message = StoreMessage(apiMessage: message, accountPeerId: account.peerId, peerIsForum: peer.isForum) {
if let message = StoreMessage(apiMessage: message, accountPeerId: accountPeerId, peerIsForum: peer.isForum) {
if case let .Id(id) = message.id {
topicId = Int64(id.id)
}
@ -249,12 +253,12 @@ func _internal_createForumChannelTopic(account: Account, peerId: PeerId, title:
}
if let topicId {
return account.postbox.transaction { transaction -> Void in
return postbox.transaction { transaction -> Void in
transaction.removeHole(peerId: peerId, threadId: topicId, namespace: Namespaces.Message.Cloud, space: .everywhere, range: 1 ... (Int32.max - 1))
}
|> castError(CreateForumChannelTopicError.self)
|> mapToSignal { _ -> Signal<Int64, CreateForumChannelTopicError> in
return resolveForumThreads(accountPeerId: account.peerId, postbox: account.postbox, source: .network(account.network), additionalPeers: AccumulatedPeers(), ids: [PeerAndBoundThreadId(peerId: peerId, threadId: topicId)])
return resolveForumThreads(accountPeerId: accountPeerId, postbox: postbox, source: .network(network), additionalPeers: AccumulatedPeers(), ids: [PeerAndBoundThreadId(peerId: peerId, threadId: topicId)])
|> castError(CreateForumChannelTopicError.self)
|> map { _ -> Int64 in
return topicId

View File

@ -32,21 +32,24 @@ private enum PendingMessageState {
case uploading(groupId: Int64?)
case waitingToBeSent(groupId: Int64?, content: PendingMessageUploadedContentAndReuploadInfo)
case sending(groupId: Int64?)
case waitingForNewTopic(message: Message)
var groupId: Int64? {
switch self {
case .none:
return nil
case let .collectingInfo(message):
return message.groupingKey
case let .waitingForUploadToStart(groupId, _):
return groupId
case let .uploading(groupId):
return groupId
case let .waitingToBeSent(groupId, _):
return groupId
case let .sending(groupId):
return groupId
case .none:
return nil
case let .collectingInfo(message):
return message.groupingKey
case let .waitingForUploadToStart(groupId, _):
return groupId
case let .uploading(groupId):
return groupId
case let .waitingToBeSent(groupId, _):
return groupId
case let .sending(groupId):
return groupId
case let .waitingForNewTopic(message):
return message.groupingKey
}
}
}
@ -115,6 +118,20 @@ public struct PeerPendingMessageDelivered {
private final class PeerPendingMessagesSummaryContext {
var messageDeliveredSubscribers = Bag<([PeerPendingMessageDelivered]) -> Void>()
var messageFailedSubscribers = Bag<(PendingMessageFailureReason) -> Void>()
var newTopicEvents = Bag<(PendingMessageManager.NewTopicEvent) -> Void>()
var isEmpty: Bool {
if !self.messageDeliveredSubscribers.isEmpty {
return false
}
if !self.messageFailedSubscribers.isEmpty {
return false
}
if !self.newTopicEvents.isEmpty {
return false
}
return true
}
}
private enum PendingMessageResult {
@ -186,6 +203,11 @@ private final class CorrelationIdToSentMessageId {
}
public final class PendingMessageManager {
public enum NewTopicEvent {
case willMove(fromThreadId: Int64, toThreadId: Int64)
case didMove(fromThreadId: Int64, toThreadId: Int64)
}
private let network: Network
private let postbox: Postbox
private let accountPeerId: PeerId
@ -206,6 +228,8 @@ public final class PendingMessageManager {
private var pendingMessageIds = Set<MessageId>()
private let beginSendingMessagesDisposables = DisposableSet()
private var newTopicDisposables: [PeerId: Disposable] = [:]
private var peerSummaryContexts: [PeerId: PeerPendingMessagesSummaryContext] = [:]
var transformOutgoingMessageMedia: TransformOutgoingMessageMedia?
@ -226,6 +250,9 @@ public final class PendingMessageManager {
deinit {
self.beginSendingMessagesDisposables.dispose()
for (_, disposable) in self.newTopicDisposables {
disposable.dispose()
}
}
func updatePendingMessageIds(_ messageIds: Set<MessageId>) {
@ -422,9 +449,7 @@ public final class PendingMessageManager {
assert(strongSelf.queue.isCurrent())
Logger.shared.log("PendingMessageManager", "begin sending, continued: \(ids)")
Logger.shared.log("PendingMessageManager", "beginSendingMessages messages.count: \(messages.count)")
for message in messages.filter({ !$0.flags.contains(.Sending) }).sorted(by: { $0.id < $1.id }) {
guard let messageContext = strongSelf.messageContexts[message.id] else {
@ -435,14 +460,106 @@ public final class PendingMessageManager {
messageContext.activityType = uploadActivityTypeForMessage(message)
}
messageContext.threadId = message.threadId
strongSelf.collectUploadingInfo(messageContext: messageContext, message: message)
if messageContext.threadId == Message.newTopicThreadId {
strongSelf.createNewTopic(messageContext: messageContext, message: message)
} else {
strongSelf.collectUploadingInfo(messageContext: messageContext, message: message)
}
}
var messagesToUpload: [(PendingMessageContext, Message, PendingMessageUploadedContentType, Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>)] = []
var messagesToForward: [PeerIdAndNamespace: [(PendingMessageContext, Message, ForwardSourceInfoAttribute)]] = [:]
Logger.shared.log("PendingMessageManager", "beginSendingMessages messageContexts.count: \(strongSelf.messageContexts.count)")
for (messageContext, _) in strongSelf.messageContexts.values.compactMap({ messageContext -> (PendingMessageContext, Message)? in
if case let .waitingForNewTopic(message) = messageContext.state {
return (messageContext, message)
} else {
return nil
}
}).sorted(by: { lhs, rhs in
return lhs.1.index < rhs.1.index
}) {
if case let .waitingForNewTopic(message) = messageContext.state {
let messagePeerId = message.id.peerId
if strongSelf.newTopicDisposables[messagePeerId] == nil {
let disposable = MetaDisposable()
strongSelf.newTopicDisposables[messagePeerId] = disposable
disposable.set(_internal_createForumChannelTopic(
postbox: strongSelf.postbox,
network: strongSelf.network,
stateManager: strongSelf.stateManager,
accountPeerId: strongSelf.accountPeerId,
peerId: message.id.peerId,
title: "Topic #\(message.stableId)",
iconColor: 0,
iconFileId: nil
).startStrict(next: { [weak strongSelf] topicId in
guard let strongSelf else {
return
}
var moveMessageIds: [MessageId] = []
for (_, messageContext) in strongSelf.messageContexts {
if case let .waitingForNewTopic(message) = messageContext.state {
if message.id.peerId == messagePeerId {
moveMessageIds.append(message.id)
}
}
}
if !moveMessageIds.isEmpty {
if let context = strongSelf.peerSummaryContexts[messagePeerId] {
for subscriber in context.newTopicEvents.copyItems() {
subscriber(.willMove(fromThreadId: Message.newTopicThreadId, toThreadId: topicId))
}
}
let _ = (strongSelf.postbox.transaction { transaction -> [Message] in
var result: [Message] = []
for id in moveMessageIds {
transaction.updateMessage(id, update: { currentMessage in
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
}
return .update(StoreMessage(id: id, customStableId: nil, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: topicId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media))
})
if let message = transaction.getMessage(id) {
result.append(message)
}
}
return result
}
|> deliverOn(strongSelf.queue)).startStandalone(next: { [weak strongSelf] messages in
guard let strongSelf else {
return
}
for message in messages {
if let context = strongSelf.messageContexts[message.id] {
if case .waitingForNewTopic = context.state {
context.state = .collectingInfo(message: message)
}
}
}
strongSelf.newTopicDisposables[messagePeerId]?.dispose()
strongSelf.newTopicDisposables[messagePeerId] = nil
strongSelf.beginSendingMessages(messages.map(\.id))
if let context = strongSelf.peerSummaryContexts[messagePeerId] {
for subscriber in context.newTopicEvents.copyItems() {
subscriber(.didMove(fromThreadId: Message.newTopicThreadId, toThreadId: topicId))
}
}
})
}
}, error: { _ in
//TODO:release handle errors
}))
}
}
}
for (messageContext, _) in strongSelf.messageContexts.values.compactMap({ messageContext -> (PendingMessageContext, Message)? in
if case let .collectingInfo(message) = messageContext.state {
@ -486,7 +603,6 @@ public final class PendingMessageManager {
}
Logger.shared.log("PendingMessageManager", "beginSendingMessages messagesToUpload.count: \(messagesToUpload.count)")
for (messageContext, message, type, contentUploadSignal) in messagesToUpload {
if let paidStarsAttribute = message.paidStarsAttribute, paidStarsAttribute.postponeSending {
@ -596,28 +712,32 @@ public final class PendingMessageManager {
loop: for (id, context) in self.messageContexts {
switch context.state {
case .none:
continue loop
case let .collectingInfo(message):
if message.groupingKey == groupId {
return nil
}
case let .waitingForUploadToStart(contextGroupId, _):
if contextGroupId == groupId {
return nil
}
case let .uploading(contextGroupId):
if contextGroupId == groupId {
return nil
}
case let .waitingToBeSent(contextGroupId, content):
if contextGroupId == groupId {
result.append((context, id, content))
}
case let .sending(contextGroupId):
if contextGroupId == groupId {
return nil
}
case .none:
continue loop
case let .collectingInfo(message):
if message.groupingKey == groupId {
return nil
}
case let .waitingForUploadToStart(contextGroupId, _):
if contextGroupId == groupId {
return nil
}
case let .uploading(contextGroupId):
if contextGroupId == groupId {
return nil
}
case let .waitingToBeSent(contextGroupId, content):
if contextGroupId == groupId {
result.append((context, id, content))
}
case let .sending(contextGroupId):
if contextGroupId == groupId {
return nil
}
case let .waitingForNewTopic(message):
if message.groupingKey == groupId {
return nil
}
}
}
@ -665,6 +785,10 @@ public final class PendingMessageManager {
}))
}
private func createNewTopic(messageContext: PendingMessageContext, message: Message) {
messageContext.state = .waitingForNewTopic(message: message)
}
private func collectUploadingInfo(messageContext: PendingMessageContext, message: Message) {
messageContext.state = .collectingInfo(message: message)
}
@ -2030,7 +2154,7 @@ public final class PendingMessageManager {
self.queue.async {
if let current = self.peerSummaryContexts[peerId] {
current.messageDeliveredSubscribers.remove(index)
if current.messageDeliveredSubscribers.isEmpty {
if current.isEmpty {
self.peerSummaryContexts.removeValue(forKey: peerId)
}
}
@ -2063,7 +2187,40 @@ public final class PendingMessageManager {
self.queue.async {
if let current = self.peerSummaryContexts[peerId] {
current.messageFailedSubscribers.remove(index)
if current.messageFailedSubscribers.isEmpty {
if current.isEmpty {
self.peerSummaryContexts.removeValue(forKey: peerId)
}
}
}
})
}
return disposable
}
}
public func newTopicEvents(peerId: PeerId) -> Signal<NewTopicEvent, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.queue.async {
let summaryContext: PeerPendingMessagesSummaryContext
if let current = self.peerSummaryContexts[peerId] {
summaryContext = current
} else {
summaryContext = PeerPendingMessagesSummaryContext()
self.peerSummaryContexts[peerId] = summaryContext
}
let index = summaryContext.newTopicEvents.add({ reason in
subscriber.putNext(reason)
})
disposable.set(ActionDisposable {
self.queue.async {
if let current = self.peerSummaryContexts[peerId] {
current.newTopicEvents.remove(index)
if current.isEmpty {
self.peerSummaryContexts.removeValue(forKey: peerId)
}
}

View File

@ -10,13 +10,6 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = {
]
}
var messageThreadHoles: [PeerId.Namespace: [MessageId.Namespace]] = [:]
for peerNamespace in peerIdNamespacesWithInitialCloudMessageHoles {
messageThreadHoles[peerNamespace] = [
Namespaces.Message.Cloud
]
}
// To avoid upgrading the database, **new** tags can be added here
// Uninitialized peers will fill the info using messageHoles
var upgradedMessageHoles: [PeerId.Namespace: [MessageId.Namespace: Set<MessageTags>]] = [:]
@ -42,7 +35,15 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = {
),
messageHoles: messageHoles,
upgradedMessageHoles: upgradedMessageHoles,
messageThreadHoles: messageThreadHoles,
messageThreadHoles: { peerIdNamespace, threadId in
if threadId == Message.newTopicThreadId {
return nil
}
if peerIdNamespacesWithInitialCloudMessageHoles.contains(peerIdNamespace) {
return [Namespaces.Message.Cloud]
}
return nil
},
existingMessageTags: MessageTags.all,
messageTagsWithSummary: [.unseenPersonalMessage, .pinned, .video, .photo, .gif, .music, .voiceOrInstantVideo, .webPage, .file, .unseenReaction],
messageTagsWithThreadSummary: [.unseenPersonalMessage, .unseenReaction],

View File

@ -1,6 +1,8 @@
import Postbox
public final class EngineMessage: Equatable {
public static let newTopicThreadId: Int64 = Message.newTopicThreadId
public typealias Id = MessageId
public typealias StableId = UInt32
public typealias Index = MessageIndex

View File

@ -128,6 +128,7 @@ public enum PresentationResourceKey: Int32 {
case chatListGeneralTopicIcon
case chatListGeneralTopicTemplateIcon
case chatListNewTopicTemplateIcon
case chatListGeneralTopicSmallIcon
case searchAdIcon

View File

@ -451,6 +451,23 @@ public struct PresentationResourcesChatList {
})
}
public static func newTopicTemplateIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatListNewTopicTemplateIcon.rawValue, { theme in
guard let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: .white) else {
return nil
}
let size = CGSize(width: 24.0, height: 24.0)
return generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
defer {
UIGraphicsPopContext()
}
image.draw(in: CGRect(origin: CGPoint(), size: size))
})?.withRenderingMode(.alwaysTemplate)
})
}
public static func statusAutoremoveIcon(_ theme: PresentationTheme, isActive: Bool) -> UIImage? {
return theme.image(PresentationResourceParameterKey.statusAutoremoveIcon(isActive: isActive), { theme in
return generateTintedImage(image: UIImage(bundleImageName: isActive ? "Chat List/StatusIconAutoremoveOn" : "Chat List/StatusIconAutoremoveOff"), color: isActive ? theme.list.itemAccentColor : theme.list.itemSecondaryTextColor)

View File

@ -392,6 +392,8 @@ public final class ChatSideTopicsPanel: Component {
} else {
avatarIconContent = .topic(title: String(threadData.info.title.prefix(1)), color: threadData.info.iconColor, size: iconSize)
}
} else if topicId == EngineMessage.newTopicThreadId {
avatarIconContent = .image(image: PresentationResourcesChatList.newTopicTemplateIcon(component.theme), tintColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.rootController.navigationBar.controlColor)
} else {
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicTemplateIcon(component.theme), tintColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.rootController.navigationBar.controlColor)
}
@ -435,6 +437,9 @@ public final class ChatSideTopicsPanel: Component {
let _ = topicId
if let threadData = component.item.item.threadData {
titleText = threadData.info.title
} else if topicId == EngineMessage.newTopicThreadId {
//TODO:localize
titleText = "New Chat"
} else {
titleText = " "
}
@ -830,6 +835,8 @@ public final class ChatSideTopicsPanel: Component {
} else {
avatarIconContent = .topic(title: String(threadData.info.title.prefix(1)), color: threadData.info.iconColor, size: iconSize)
}
} else if topicId == EngineMessage.newTopicThreadId {
avatarIconContent = .image(image: PresentationResourcesChatList.newTopicTemplateIcon(component.theme), tintColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.rootController.navigationBar.controlColor)
} else {
avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicTemplateIcon(component.theme), tintColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.rootController.navigationBar.controlColor)
}
@ -867,6 +874,9 @@ public final class ChatSideTopicsPanel: Component {
let _ = topicId
if let threadData = component.item.item.threadData {
titleText = threadData.info.title
} else if topicId == EngineMessage.newTopicThreadId {
//TODO:localize
titleText = "New Chat"
} else {
titleText = " "
}
@ -1736,11 +1746,45 @@ public final class ChatSideTopicsPanel: Component {
self.rawItems.removeAll()
for item in chatList.items.reversed() {
if case .botForum = component.kind, case let .forum(topicId) = item.id, topicId == 1 {
#if DEBUG
#else
continue
#endif
}
self.rawItems.append(Item(item: item))
}
if case .botForum = component.kind, !self.rawItems.contains(where: { item in
if case let .forum(topicId) = item.id {
return topicId == EngineMessage.newTopicThreadId
} else {
return false
}
}) {
self.rawItems.insert(Item(item: EngineChatList.Item(
id: .forum(EngineMessage.newTopicThreadId),
index: EngineChatList.Item.Index.forum(pinnedIndex: .none, timestamp: Int32.max - 1, threadId: EngineMessage.newTopicThreadId, namespace: Namespaces.Message.Local, id: 1),
messages: [],
readCounters: nil,
isMuted: false,
draft: nil,
threadData: nil,
renderedPeer: EngineRenderedPeer(peerId: peerId, peers: [:], associatedMedia: [:]),
presence: nil,
hasUnseenMentions: false,
hasUnseenReactions: false,
forumTopicData: nil,
topForumTopicItems: [],
hasFailed: false,
isContact: false,
autoremoveTimeout: nil,
storyStats: nil,
displayAsTopicList: false,
isPremiumRequiredToMessage: false,
mediaDraftContentType: nil
)), at: 0)
}
if self.reorderingItems != nil {
self.reorderingItems = self.rawItems
}

View File

@ -130,6 +130,9 @@ extension ChatControllerImpl {
self.contentDataDisposable?.dispose()
self.newTopicEventsDisposable?.dispose()
self.newTopicEventsDisposable = nil
let configuration: Signal<ChatControllerImpl.ContentData.Configuration, NoError> = self.presentationInterfaceStatePromise.get()
|> map { presentationInterfaceState -> ChatControllerImpl.ContentData.Configuration in
return ChatControllerImpl.ContentData.Configuration(
@ -166,9 +169,9 @@ extension ChatControllerImpl {
if let historyNodeData = contentData.state.historyNodeData {
self.updateChatLocationToOther(chatLocation: historyNodeData.chatLocation)
return
} else if case let .botForumThread(_, threadId) = self.subject {
} else if case let .botForumThread(linkedForumId, threadId) = self.subject {
self.subject = nil
self.updateChatLocationThread(threadId: threadId)
self.updateInitialChatBotForumLocationThread(linkedForumId: linkedForumId, threadId: threadId)
return
}
@ -206,6 +209,25 @@ extension ChatControllerImpl {
self.contentDataUpdated(synchronous: false, forceAnimation: false, previousState: previousState)
}
})
if self.newTopicEventsDisposable == nil, let peerId = chatLocation.peerId, chatLocation.threadId == EngineMessage.newTopicThreadId {
self.newTopicEventsDisposable = (self.context.account.pendingMessageManager.newTopicEvents(peerId: peerId)
|> mapToSignal { event -> Signal<Int64, NoError> in
if case let .didMove(fromThreadId, toThreadId) = event {
if fromThreadId == EngineMessage.newTopicThreadId {
return .single(toThreadId)
}
}
return .never()
}
|> take(1)
|> deliverOnMainQueue).startStrict(next: { [weak self] threadId in
guard let self else {
return
}
self.updateInitialChatBotForumLocationThread(linkedForumId: peerId, threadId: threadId)
})
}
})
}

View File

@ -258,6 +258,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var contentData: ChatControllerImpl.ContentData?
let contentDataReady = ValuePromise<Bool>(false, ignoreRepeated: true)
var contentDataDisposable: Disposable?
var newTopicEventsDisposable: Disposable?
var didHandlePerformDismissAction: Bool = false
var didInitializePersistentPeerInterfaceData: Bool = false
@ -703,6 +704,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.stickerSettings = ChatInterfaceStickerSettings()
var subject = subject
if case .botForumThread = subject {
subject = nil
}
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: mode, chatLocation: chatLocation, subject: subject, peerNearbyData: peerNearbyData, greetingData: context.prefetchManager?.preloadedGreetingSticker, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil)
if case let .customChatContents(customChatContents) = subject {
@ -6181,6 +6187,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.updateChatLocationThreadDisposable?.dispose()
self.accountPeerDisposable?.dispose()
self.contentDataDisposable?.dispose()
self.newTopicEventsDisposable?.dispose()
self.updateMessageTodoDisposables?.dispose()
self.preloadNextChatPeerIdDisposable.dispose()
}
@ -10146,6 +10153,52 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
}
func updateInitialChatBotForumLocationThread(linkedForumId: EnginePeer.Id, threadId: Int64) {
if self.isUpdatingChatLocationThread {
assertionFailure()
return
}
self.saveInterfaceState()
self.chatDisplayNode.dismissTextInput()
let updatedChatLocation: ChatLocation = .replyThread(message: ChatReplyThreadMessage(
peerId: linkedForumId,
threadId: threadId,
channelMessageId: nil,
isChannelPost: false,
isForumPost: true,
isMonoforumPost: false,
maxMessage: nil,
maxReadIncomingMessageId: nil,
maxReadOutgoingMessageId: nil,
unreadCount: 0,
initialFilledHoles: IndexSet(),
initialAnchor: .automatic,
isNotAvailable: false
))
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
let historyNode = self.chatDisplayNode.createHistoryNodeForChatLocation(chatLocation: updatedChatLocation, chatLocationContextHolder: chatLocationContextHolder)
self.isUpdatingChatLocationThread = true
self.reloadChatLocation(chatLocation: updatedChatLocation, chatLocationContextHolder: chatLocationContextHolder, historyNode: historyNode, apply: { [weak self, weak historyNode] apply in
guard let self, let historyNode else {
return
}
self.currentChatSwitchDirection = nil
self.chatLocation = updatedChatLocation
historyNode.areContentAnimationsEnabled = true
self.chatDisplayNode.prepareSwitchToChatLocation(historyNode: historyNode, animationDirection: nil)
apply(true)
self.currentChatSwitchDirection = nil
self.isUpdatingChatLocationThread = false
})
}
public func updateChatLocationThread(threadId: Int64?, animationDirection: ChatControllerAnimateInnerChatSwitchDirection? = nil) {
Task { @MainActor [weak self] in
guard let self else {

View File

@ -1569,6 +1569,10 @@ extension ChatControllerImpl {
infoContextActionIsEnabled = true
}
strongSelf.state.infoAvatar = .emojiStatus(content: avatarContent, contextActionIsEnabled: infoContextActionIsEnabled)
} else if chatLocation.threadId == EngineMessage.newTopicThreadId {
//TODO:localize
strongSelf.state.chatTitleContent = .custom("New Chat", nil, false)
strongSelf.state.infoAvatar = nil
} else {
strongSelf.state.chatTitleContent = .replyThread(type: replyThreadType, count: count)
}

View File

@ -1381,7 +1381,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
let currentViewVersion = self.currentViewVersion
let historyViewUpdate: Signal<(ChatHistoryViewUpdate, Int, ChatHistoryLocationInput?, ClosedRange<Int32>?, Set<MessageId>), NoError>
var historyViewUpdate: Signal<(ChatHistoryViewUpdate, Int, ChatHistoryLocationInput?, ClosedRange<Int32>?, Set<MessageId>), NoError>
var isFirstTime = true
var updateAllOnEachVersion = false
if case let .custom(messages, at, quote, _) = self.source {
@ -1746,6 +1746,33 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
}
|> distinctUntilChanged
let stopHistoryViewUpdates: Signal<Bool, NoError>
if let peerId = chatLocation.peerId, chatLocation.threadId == EngineMessage.newTopicThreadId {
stopHistoryViewUpdates = Signal<Bool, NoError>.single(false)
|> then(
self.context.account.pendingMessageManager.newTopicEvents(peerId: peerId)
|> mapToSignal { event -> Signal<Bool, NoError> in
if case .willMove(EngineMessage.newTopicThreadId, _) = event {
return .single(true)
} else {
return .never()
}
}
|> take(1)
)
} else {
stopHistoryViewUpdates = .single(false)
}
let historyViewUpdateValue = historyViewUpdate
historyViewUpdate = stopHistoryViewUpdates |> mapToSignal { value in
if value {
return .never()
} else {
return historyViewUpdateValue
}
}
let startTime = CFAbsoluteTimeGetCurrent()
var measure_isFirstTime = true
let messageViewQueue = Queue.mainQueue()