mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-08 19:10:53 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
2d04763fae
@ -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)
|
||||
|
@ -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?
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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],
|
||||
|
@ -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
|
||||
|
@ -128,6 +128,7 @@ public enum PresentationResourceKey: Int32 {
|
||||
|
||||
case chatListGeneralTopicIcon
|
||||
case chatListGeneralTopicTemplateIcon
|
||||
case chatListNewTopicTemplateIcon
|
||||
case chatListGeneralTopicSmallIcon
|
||||
|
||||
case searchAdIcon
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user