diff --git a/Telegram/SiriIntents/IntentMessages.swift b/Telegram/SiriIntents/IntentMessages.swift index 935c3a1db4..37360288e3 100644 --- a/Telegram/SiriIntents/IntentMessages.swift +++ b/Telegram/SiriIntents/IntentMessages.swift @@ -53,7 +53,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> { } if !isMuted && hasUnread { - signals.append(account.postbox.aroundMessageHistoryViewForLocation(.peer(peerId: index.messageIndex.id.peerId, threadId: nil), anchor: .upperBound, ignoreMessagesInTimestampRange: nil, count: 10, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: Set(), tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: .combinedLocation) + signals.append(account.postbox.aroundMessageHistoryViewForLocation(.peer(peerId: index.messageIndex.id.peerId, threadId: nil), anchor: .upperBound, ignoreMessagesInTimestampRange: nil, count: 10, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: Set(), tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .not(Namespaces.Message.allNonRegular), orderStatistics: .combinedLocation) |> take(1) |> map { view -> [INMessage] in var messages: [INMessage] = [] diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 230c49dd13..66dde6dd3d 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -805,7 +805,7 @@ public enum ChatControllerPresentationMode: Equatable { public enum ChatInputTextCommand: Equatable { case command(PeerCommand) - case shortcut(QuickReplyMessageShortcut) + case shortcut(ShortcutMessageList.Item) } public struct ChatInputQueryCommandsResult: Equatable { @@ -1077,6 +1077,7 @@ public enum ChatHistoryListSource { case `default` case custom(messages: Signal<([Message], Int32, Bool), NoError>, messageId: MessageId?, quote: Quote?, loadMore: (() -> Void)?) + case customView(historyView: Signal<(MessageHistoryView, ViewUpdateType), NoError>) } public enum ChatCustomContentsKind: Equatable { @@ -1087,7 +1088,7 @@ public enum ChatCustomContentsKind: Equatable { public protocol ChatCustomContentsProtocol: AnyObject { var kind: ChatCustomContentsKind { get } - var messages: Signal<[Message], NoError> { get } + var historyView: Signal<(MessageHistoryView, ViewUpdateType), NoError> { get } var messageLimit: Int? { get } func enqueueMessages(messages: [EnqueueMessage]) diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift index 98187511ea..6b4bf1eebd 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift @@ -104,7 +104,7 @@ public final class ChatPanelInterfaceInteraction { public let togglePeerNotifications: () -> Void public let sendContextResult: (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool public let sendBotCommand: (Peer, String) -> Void - public let sendShortcut: (QuickReplyMessageShortcut) -> Void + public let sendShortcut: (Int32) -> Void public let openEditShortcuts: () -> Void public let sendBotStart: (String?) -> Void public let botSwitchChatWithPayload: (PeerId, String) -> Void @@ -219,7 +219,7 @@ public final class ChatPanelInterfaceInteraction { togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, sendBotCommand: @escaping (Peer, String) -> Void, - sendShortcut: @escaping (QuickReplyMessageShortcut) -> Void, + sendShortcut: @escaping (Int32) -> Void, openEditShortcuts: @escaping () -> Void, sendBotStart: @escaping (String?) -> Void, botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index a715bd32af..131a2319b4 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -656,8 +656,10 @@ public class GalleryController: ViewController, StandalonePresentableController, let namespaces: MessageIdNamespaces if Namespaces.Message.allScheduled.contains(message.id.namespace) { namespaces = .just(Namespaces.Message.allScheduled) + } else if Namespaces.Message.allQuickReply.contains(message.id.namespace) { + namespaces = .just(Namespaces.Message.allQuickReply) } else { - namespaces = .not(Namespaces.Message.allScheduled) + namespaces = .not(Namespaces.Message.allNonRegular) } let inputTag: HistoryViewInputTag if let customTag { @@ -1391,8 +1393,10 @@ public class GalleryController: ViewController, StandalonePresentableController, let namespaces: MessageIdNamespaces if Namespaces.Message.allScheduled.contains(message.id.namespace) { namespaces = .just(Namespaces.Message.allScheduled) + } else if Namespaces.Message.allQuickReply.contains(message.id.namespace) { + namespaces = .just(Namespaces.Message.allQuickReply) } else { - namespaces = .not(Namespaces.Message.allScheduled) + namespaces = .not(Namespaces.Message.allNonRegular) } let signal = strongSelf.context.account.postbox.aroundMessageHistoryViewForLocation(strongSelf.context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(reloadAroundIndex), ignoreMessagesInTimestampRange: nil, count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tag: tag, appendMessagesFromTheSameGroup: false, namespaces: namespaces, orderStatistics: [.combinedLocation]) |> mapToSignal { (view, _, _) -> Signal in diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 688f3ea1b6..11d90442d4 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -1184,7 +1184,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { var hintSeekable = false if let contentInfo = item.contentInfo, case let .message(message) = contentInfo { - if Namespaces.Message.allScheduled.contains(message.id.namespace) { + if Namespaces.Message.allNonRegular.contains(message.id.namespace) { disablePictureInPicture = true } else { let throttledSignal = videoNode.status diff --git a/submodules/Postbox/Sources/AdditionalMessageHistoryViewData.swift b/submodules/Postbox/Sources/AdditionalMessageHistoryViewData.swift index 9cf5cb60e6..88ccb841c3 100644 --- a/submodules/Postbox/Sources/AdditionalMessageHistoryViewData.swift +++ b/submodules/Postbox/Sources/AdditionalMessageHistoryViewData.swift @@ -1,6 +1,6 @@ import Foundation -public enum AdditionalMessageHistoryViewData { +public enum AdditionalMessageHistoryViewData: Equatable { case cachedPeerData(PeerId) case cachedPeerDataMessages(PeerId) case peerChatState(PeerId) diff --git a/submodules/Postbox/Sources/Message.swift b/submodules/Postbox/Sources/Message.swift index 58c14eda19..255cf48937 100644 --- a/submodules/Postbox/Sources/Message.swift +++ b/submodules/Postbox/Sources/Message.swift @@ -1029,7 +1029,7 @@ final class InternalStoreMessage { } } -public enum MessageIdNamespaces { +public enum MessageIdNamespaces: Equatable { case all case just(Set) case not(Set) @@ -1045,3 +1045,23 @@ public enum MessageIdNamespaces { } } } + +public struct PeerAndThreadId: Hashable { + public var peerId: PeerId + public var threadId: Int64? + + public init(peerId: PeerId, threadId: Int64?) { + self.peerId = peerId + self.threadId = threadId + } +} + +public struct MessageAndThreadId: Hashable { + public var messageId: MessageId + public var threadId: Int64? + + public init(messageId: MessageId, threadId: Int64?) { + self.messageId = messageId + self.threadId = threadId + } +} diff --git a/submodules/Postbox/Sources/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index 593d94ad7d..cca6c8d97d 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -237,7 +237,7 @@ public enum MessageHistoryViewRelativeHoleDirection: Equatable, Hashable, Custom } } -public struct MessageHistoryViewOrderStatistics: OptionSet { +public struct MessageHistoryViewOrderStatistics: OptionSet, Equatable { public var rawValue: Int32 public init(rawValue: Int32) { @@ -290,7 +290,7 @@ public enum MessageHistoryViewInput: Equatable { case external(MessageHistoryViewExternalInput) } -public enum MessageHistoryViewReadState { +public enum MessageHistoryViewReadState: Equatable { case peer([PeerId: CombinedPeerReadState]) } @@ -302,7 +302,7 @@ public enum HistoryViewInputAnchor: Equatable { case unread } -final class MutableMessageHistoryView { +final class MutableMessageHistoryView: MutablePostboxView { private(set) var peerIds: MessageHistoryViewInput private let ignoreMessagesInTimestampRange: ClosedRange? let tag: HistoryViewInputTag? @@ -310,6 +310,7 @@ final class MutableMessageHistoryView { let namespaces: MessageIdNamespaces private let orderStatistics: MessageHistoryViewOrderStatistics private let clipHoles: Bool + private let trackHoles: Bool private let anchor: HistoryViewInputAnchor fileprivate var combinedReadStates: MessageHistoryViewReadState? @@ -333,6 +334,7 @@ final class MutableMessageHistoryView { postbox: PostboxImpl, orderStatistics: MessageHistoryViewOrderStatistics, clipHoles: Bool, + trackHoles: Bool, peerIds: MessageHistoryViewInput, ignoreMessagesInTimestampRange: ClosedRange?, anchor inputAnchor: HistoryViewInputAnchor, @@ -343,13 +345,13 @@ final class MutableMessageHistoryView { namespaces: MessageIdNamespaces, count: Int, topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?], - additionalDatas: [AdditionalMessageHistoryViewDataEntry], - getMessageCountInRange: (MessageIndex, MessageIndex) -> Int32 + additionalDatas: [AdditionalMessageHistoryViewDataEntry] ) { self.anchor = inputAnchor self.orderStatistics = orderStatistics self.clipHoles = clipHoles + self.trackHoles = trackHoles self.peerIds = peerIds self.ignoreMessagesInTimestampRange = ignoreMessagesInTimestampRange self.combinedReadStates = combinedReadStates @@ -1040,6 +1042,10 @@ final class MutableMessageHistoryView { } func firstHole() -> (MessageHistoryViewHole, MessageHistoryViewRelativeHoleDirection, Int, Int64?)? { + if !self.trackHoles { + return nil + } + switch self.sampledState { case let .loading(loadingSample): switch loadingSample { @@ -1065,9 +1071,13 @@ final class MutableMessageHistoryView { } } } + + func immutableView() -> PostboxView { + return MessageHistoryView(self) + } } -public final class MessageHistoryView { +public final class MessageHistoryView: PostboxView { public let tag: HistoryViewInputTag? public let namespaces: MessageIdNamespaces public let anchorIndex: MessageHistoryAnchorIndex diff --git a/submodules/Postbox/Sources/MessageOfInterestHolesView.swift b/submodules/Postbox/Sources/MessageOfInterestHolesView.swift index 978b7b52c3..48ab8b21d8 100644 --- a/submodules/Postbox/Sources/MessageOfInterestHolesView.swift +++ b/submodules/Postbox/Sources/MessageOfInterestHolesView.swift @@ -60,7 +60,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { } } self.anchor = anchor - self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, ignoreMessagesInTimestampRange: nil, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0}) + self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, trackHoles: false, peerIds: peerIds, ignoreMessagesInTimestampRange: nil, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: []) let _ = self.updateFromView() } @@ -134,7 +134,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { case let .peer(id, threadId): peerIds = postbox.peerIdsForLocation(.peer(peerId: id, threadId: threadId), ignoreRelatedChats: false) } - self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, ignoreMessagesInTimestampRange: nil, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0}) + self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, trackHoles: false, peerIds: peerIds, ignoreMessagesInTimestampRange: nil, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: []) return self.updateFromView() } else if self.wrappedView.replay(postbox: postbox, transaction: transaction) { var reloadView = false @@ -167,7 +167,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { case let .peer(id, threadId): peerIds = postbox.peerIdsForLocation(.peer(peerId: id, threadId: threadId), ignoreRelatedChats: false) } - self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, ignoreMessagesInTimestampRange: nil, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0}) + self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, trackHoles: false, peerIds: peerIds, ignoreMessagesInTimestampRange: nil, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: []) } return self.updateFromView() diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 33201d99a7..a5be948077 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -3344,13 +3344,7 @@ final class PostboxImpl { readStates = transientReadStates } - let mutableView = MutableMessageHistoryView(postbox: self, orderStatistics: orderStatistics, clipHoles: clipHoles, peerIds: peerIds, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, anchor: anchor, combinedReadStates: readStates, transientReadStates: transientReadStates, tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: namespaces, count: count, topTaggedMessages: topTaggedMessages, additionalDatas: additionalDataEntries, getMessageCountInRange: { lowerBound, upperBound in - if case let .tag(tagMask) = tag { - return Int32(self.messageHistoryTable.getMessageCountInRange(peerId: lowerBound.id.peerId, namespace: lowerBound.id.namespace, tag: tagMask, lowerBound: lowerBound, upperBound: upperBound)) - } else { - return 0 - } - }) + let mutableView = MutableMessageHistoryView(postbox: self, orderStatistics: orderStatistics, clipHoles: clipHoles, trackHoles: true, peerIds: peerIds, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, anchor: anchor, combinedReadStates: readStates, transientReadStates: transientReadStates, tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: namespaces, count: count, topTaggedMessages: topTaggedMessages, additionalDatas: additionalDataEntries) let initialUpdateType: ViewUpdateType = .Initial diff --git a/submodules/Postbox/Sources/PostboxView.swift b/submodules/Postbox/Sources/PostboxView.swift index 7cf89152ae..eb629a5d16 100644 --- a/submodules/Postbox/Sources/PostboxView.swift +++ b/submodules/Postbox/Sources/PostboxView.swift @@ -1,9 +1,9 @@ import Foundation -public protocol PostboxView { +public protocol PostboxView: AnyObject { } -protocol MutablePostboxView { +protocol MutablePostboxView: AnyObject { func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool func immutableView() -> PostboxView @@ -16,14 +16,71 @@ final class CombinedMutableView { self.views = views } - func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool { - var updated = false + func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> (updated: Bool, updateTrackedHoles: Bool) { + var anyUpdated = false + var updateTrackedHoles = false for (_, view) in self.views { - if view.replay(postbox: postbox, transaction: transaction) { - updated = true + if let mutableView = view as? MutableMessageHistoryView { + var innerUpdated = false + + let previousPeerIds = mutableView.peerIds + + if mutableView.replay(postbox: postbox, transaction: transaction) { + innerUpdated = true + } + + var updateType: ViewUpdateType = .Generic + switch mutableView.peerIds { + case let .single(peerId, threadId): + for key in transaction.currentPeerHoleOperations.keys { + if key.peerId == peerId && key.threadId == threadId { + updateType = .FillHole + break + } + } + case .associated: + var ids = Set() + switch mutableView.peerIds { + case .single, .external: + assertionFailure() + case let .associated(mainPeerId, associatedId): + ids.insert(mainPeerId) + if let associatedId = associatedId { + ids.insert(associatedId.peerId) + } + } + + if !ids.isEmpty { + for key in transaction.currentPeerHoleOperations.keys { + if ids.contains(key.peerId) { + updateType = .FillHole + break + } + } + } + case .external: + break + } + + mutableView.updatePeerIds(transaction: transaction) + if mutableView.peerIds != previousPeerIds { + updateType = .UpdateVisible + + let _ = mutableView.refreshDueToExternalTransaction(postbox: postbox) + innerUpdated = true + } + + if innerUpdated { + anyUpdated = true + updateTrackedHoles = true + let _ = updateType + //pipe.putNext((MessageHistoryView(mutableView), updateType)) + } + } else if view.replay(postbox: postbox, transaction: transaction) { + anyUpdated = true } } - return updated + return (anyUpdated, updateTrackedHoles) } func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { diff --git a/submodules/Postbox/Sources/ViewTracker.swift b/submodules/Postbox/Sources/ViewTracker.swift index 12d96f1118..ff3f3252c8 100644 --- a/submodules/Postbox/Sources/ViewTracker.swift +++ b/submodules/Postbox/Sources/ViewTracker.swift @@ -1,7 +1,7 @@ import Foundation import SwiftSignalKit -public enum ViewUpdateType : Equatable { +public enum ViewUpdateType: Equatable { case Initial case InitialUnread(MessageIndex) case Generic @@ -351,10 +351,6 @@ final class ViewTracker { self.updateTrackedChatListHoles() - if updateTrackedHoles { - self.updateTrackedHoles() - } - if self.unsentMessageView.replay(transaction.unsentMessageOperations) { self.unsentViewUpdated() } @@ -414,9 +410,14 @@ final class ViewTracker { } for (mutableView, pipe) in self.combinedViews.copyItems() { - if mutableView.replay(postbox: postbox, transaction: transaction) { + let result = mutableView.replay(postbox: postbox, transaction: transaction) + + if result.updated { pipe.putNext(mutableView.immutableView()) } + if result.updateTrackedHoles { + updateTrackedHoles = true + } } for (view, pipe) in self.failedMessageIdsViews.copyItems() { @@ -425,6 +426,10 @@ final class ViewTracker { } } + if updateTrackedHoles { + self.updateTrackedHoles() + } + self.updateTrackedForumTopicListHoles() } @@ -486,6 +491,26 @@ final class ViewTracker { firstHolesAndTags.insert(MessageHistoryHolesViewEntry(hole: hole, direction: direction, space: space, count: count, userId: userId)) } } + for (view, _) in self.combinedViews.copyItems() { + for (_, subview) in view.views { + if let subview = subview as? MutableMessageHistoryView { + if let (hole, direction, count, userId) = subview.firstHole() { + let space: MessageHistoryHoleOperationSpace + if let tag = subview.tag { + switch tag { + case let .tag(value): + space = .tag(value) + case let .customTag(value, regularTag): + space = .customTag(value, regularTag) + } + } else { + space = .everywhere + } + firstHolesAndTags.insert(MessageHistoryHolesViewEntry(hole: hole, direction: direction, space: space, count: count, userId: userId)) + } + } + } + } if self.messageHistoryHolesView.update(firstHolesAndTags) { for subscriber in self.messageHistoryHolesViewSubscribers.copyItems() { diff --git a/submodules/Postbox/Sources/Views.swift b/submodules/Postbox/Sources/Views.swift index 9fd7e8b9e4..c3b0e95f18 100644 --- a/submodules/Postbox/Sources/Views.swift +++ b/submodules/Postbox/Sources/Views.swift @@ -1,6 +1,52 @@ import Foundation public enum PostboxViewKey: Hashable { + public struct HistoryView: Equatable { + public var peerId: PeerId + public var threadId: Int64? + public var clipHoles: Bool + public var trackHoles: Bool + public var orderStatistics: MessageHistoryViewOrderStatistics + public var ignoreMessagesInTimestampRange: ClosedRange? + public var anchor: HistoryViewInputAnchor + public var combinedReadStates: MessageHistoryViewReadState? + public var transientReadStates: MessageHistoryViewReadState? + public var tag: HistoryViewInputTag? + public var appendMessagesFromTheSameGroup: Bool + public var namespaces: MessageIdNamespaces + public var count: Int + + public init( + peerId: PeerId, + threadId: Int64?, + clipHoles: Bool, + trackHoles: Bool, + orderStatistics: MessageHistoryViewOrderStatistics = [], + ignoreMessagesInTimestampRange: ClosedRange? = nil, + anchor: HistoryViewInputAnchor, + combinedReadStates: MessageHistoryViewReadState? = nil, + transientReadStates: MessageHistoryViewReadState? = nil, + tag: HistoryViewInputTag? = nil, + appendMessagesFromTheSameGroup: Bool, + namespaces: MessageIdNamespaces, + count: Int + ) { + self.peerId = peerId + self.threadId = threadId + self.clipHoles = clipHoles + self.trackHoles = trackHoles + self.orderStatistics = orderStatistics + self.ignoreMessagesInTimestampRange = ignoreMessagesInTimestampRange + self.anchor = anchor + self.combinedReadStates = combinedReadStates + self.transientReadStates = transientReadStates + self.tag = tag + self.appendMessagesFromTheSameGroup = appendMessagesFromTheSameGroup + self.namespaces = namespaces + self.count = count + } + } + case itemCollectionInfos(namespaces: [ItemCollectionId.Namespace]) case itemCollectionIds(namespaces: [ItemCollectionId.Namespace]) case itemCollectionInfo(id: ItemCollectionId) @@ -50,6 +96,7 @@ public enum PostboxViewKey: Hashable { case savedMessagesIndex(peerId: PeerId) case savedMessagesStats(peerId: PeerId) case chatInterfaceState(peerId: PeerId) + case historyView(HistoryView) public func hash(into hasher: inout Hasher) { switch self { @@ -168,6 +215,10 @@ public enum PostboxViewKey: Hashable { hasher.combine(peerId) case let .chatInterfaceState(peerId): hasher.combine(peerId) + case let .historyView(historyView): + hasher.combine(20) + hasher.combine(historyView.peerId) + hasher.combine(historyView.threadId) } } @@ -467,6 +518,12 @@ public enum PostboxViewKey: Hashable { } else { return false } + case let .historyView(historyView): + if case .historyView(historyView) = rhs { + return true + } else { + return false + } } } } @@ -571,5 +628,23 @@ func postboxViewForKey(postbox: PostboxImpl, key: PostboxViewKey) -> MutablePost return MutableMessageHistorySavedMessagesStatsView(postbox: postbox, peerId: peerId) case let .chatInterfaceState(peerId): return MutableChatInterfaceStateView(postbox: postbox, peerId: peerId) + case let .historyView(historyView): + return MutableMessageHistoryView( + postbox: postbox, + orderStatistics: historyView.orderStatistics, + clipHoles: historyView.clipHoles, + trackHoles: historyView.trackHoles, + peerIds: .single(peerId: historyView.peerId, threadId: historyView.threadId), + ignoreMessagesInTimestampRange: historyView.ignoreMessagesInTimestampRange, + anchor: historyView.anchor, + combinedReadStates: historyView.combinedReadStates, + transientReadStates: historyView.transientReadStates, + tag: historyView.tag, + appendMessagesFromTheSameGroup: historyView.appendMessagesFromTheSameGroup, + namespaces: historyView.namespaces, + count: historyView.count, + topTaggedMessages: [:], + additionalDatas: [] + ) } } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 47f52d79d8..763c13f933 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -101,7 +101,12 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-944407322] = { return Api.BotMenuButton.parse_botMenuButton($0) } dict[1113113093] = { return Api.BotMenuButton.parse_botMenuButtonCommands($0) } dict[1966318984] = { return Api.BotMenuButton.parse_botMenuButtonDefault($0) } - dict[-1104414653] = { return Api.BusinessLocation.parse_businessLocation($0) } + dict[-1697809899] = { return Api.BusinessAwayMessage.parse_businessAwayMessage($0) } + dict[-910564679] = { return Api.BusinessAwayMessageSchedule.parse_businessAwayMessageScheduleAlways($0) } + dict[-867328308] = { return Api.BusinessAwayMessageSchedule.parse_businessAwayMessageScheduleCustom($0) } + dict[-1007487743] = { return Api.BusinessAwayMessageSchedule.parse_businessAwayMessageScheduleOutsideWorkHours($0) } + dict[-1600596660] = { return Api.BusinessGreetingMessage.parse_businessGreetingMessage($0) } + dict[-1403249929] = { return Api.BusinessLocation.parse_businessLocation($0) } dict[302717625] = { return Api.BusinessWeeklyOpen.parse_businessWeeklyOpen($0) } dict[-1936543592] = { return Api.BusinessWorkHours.parse_businessWorkHours($0) } dict[1462101002] = { return Api.CdnConfig.parse_cdnConfig($0) } @@ -300,6 +305,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-459324] = { return Api.InputBotInlineResult.parse_inputBotInlineResultDocument($0) } dict[1336154098] = { return Api.InputBotInlineResult.parse_inputBotInlineResultGame($0) } dict[-1462213465] = { return Api.InputBotInlineResult.parse_inputBotInlineResultPhoto($0) } + dict[-831530424] = { return Api.InputBusinessAwayMessage.parse_inputBusinessAwayMessage($0) } + dict[2102015497] = { return Api.InputBusinessGreetingMessage.parse_inputBusinessGreetingMessage($0) } dict[-212145112] = { return Api.InputChannel.parse_inputChannel($0) } dict[-292807034] = { return Api.InputChannel.parse_inputChannelEmpty($0) } dict[1536380829] = { return Api.InputChannel.parse_inputChannelFromMessage($0) } @@ -401,6 +408,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-380694650] = { return Api.InputPrivacyRule.parse_inputPrivacyValueDisallowChatParticipants($0) } dict[195371015] = { return Api.InputPrivacyRule.parse_inputPrivacyValueDisallowContacts($0) } dict[-1877932953] = { return Api.InputPrivacyRule.parse_inputPrivacyValueDisallowUsers($0) } + dict[609840449] = { return Api.InputQuickReplyShortcut.parse_inputQuickReplyShortcut($0) } + dict[18418929] = { return Api.InputQuickReplyShortcut.parse_inputQuickReplyShortcutId($0) } dict[583071445] = { return Api.InputReplyTo.parse_inputReplyToMessage($0) } dict[1484862010] = { return Api.InputReplyTo.parse_inputReplyToStory($0) } dict[1399317950] = { return Api.InputSecureFile.parse_inputSecureFile($0) } @@ -710,6 +719,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-463335103] = { return Api.PrivacyRule.parse_privacyValueDisallowUsers($0) } dict[32685898] = { return Api.PublicForward.parse_publicForwardMessage($0) } dict[-302797360] = { return Api.PublicForward.parse_publicForwardStory($0) } + dict[110563371] = { return Api.QuickReply.parse_quickReply($0) } dict[-1992950669] = { return Api.Reaction.parse_reactionCustomEmoji($0) } dict[455247544] = { return Api.Reaction.parse_reactionEmoji($0) } dict[2046153753] = { return Api.Reaction.parse_reactionEmpty($0) } @@ -939,7 +949,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1656358105] = { return Api.Update.parse_updateNewChannelMessage($0) } dict[314359194] = { return Api.Update.parse_updateNewEncryptedMessage($0) } dict[522914557] = { return Api.Update.parse_updateNewMessage($0) } - dict[-1386034803] = { return Api.Update.parse_updateNewQuickReply($0) } + dict[-180508905] = { return Api.Update.parse_updateNewQuickReply($0) } dict[967122427] = { return Api.Update.parse_updateNewScheduledMessage($0) } dict[1753886890] = { return Api.Update.parse_updateNewStickerSet($0) } dict[-1094555409] = { return Api.Update.parse_updateNotifySettings($0) } @@ -957,7 +967,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1751942566] = { return Api.Update.parse_updatePinnedSavedDialogs($0) } dict[-298113238] = { return Api.Update.parse_updatePrivacy($0) } dict[861169551] = { return Api.Update.parse_updatePtsChanged($0) } - dict[230929261] = { return Api.Update.parse_updateQuickReplies($0) } + dict[-112784718] = { return Api.Update.parse_updateQuickReplies($0) } dict[1040518415] = { return Api.Update.parse_updateQuickReplyMessage($0) } dict[-693004986] = { return Api.Update.parse_updateReadChannelDiscussionInbox($0) } dict[1767677564] = { return Api.Update.parse_updateReadChannelDiscussionOutbox($0) } @@ -1006,7 +1016,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1831650802] = { return Api.UrlAuthResult.parse_urlAuthResultRequest($0) } dict[559694904] = { return Api.User.parse_user($0) } dict[-742634630] = { return Api.User.parse_userEmpty($0) } - dict[-501688336] = { return Api.UserFull.parse_userFull($0) } + dict[587153029] = { return Api.UserFull.parse_userFull($0) } dict[-2100168954] = { return Api.UserProfilePhoto.parse_userProfilePhoto($0) } dict[1326562017] = { return Api.UserProfilePhoto.parse_userProfilePhotoEmpty($0) } dict[164646985] = { return Api.UserStatus.parse_userStatusEmpty($0) } @@ -1185,9 +1195,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[978610270] = { return Api.messages.Messages.parse_messagesSlice($0) } dict[863093588] = { return Api.messages.PeerDialogs.parse_peerDialogs($0) } dict[1753266509] = { return Api.messages.PeerSettings.parse_peerSettings($0) } - dict[2094438528] = { return Api.messages.QuickReplies.parse_quickReplies($0) } + dict[-963811691] = { return Api.messages.QuickReplies.parse_quickReplies($0) } dict[1603398491] = { return Api.messages.QuickReplies.parse_quickRepliesNotModified($0) } - dict[-1810973582] = { return Api.messages.QuickReply.parse_quickReply($0) } dict[-352454890] = { return Api.messages.Reactions.parse_reactions($0) } dict[-1334846497] = { return Api.messages.Reactions.parse_reactionsNotModified($0) } dict[-1999405994] = { return Api.messages.RecentStickers.parse_recentStickers($0) } @@ -1386,6 +1395,12 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.BotMenuButton: _1.serialize(buffer, boxed) + case let _1 as Api.BusinessAwayMessage: + _1.serialize(buffer, boxed) + case let _1 as Api.BusinessAwayMessageSchedule: + _1.serialize(buffer, boxed) + case let _1 as Api.BusinessGreetingMessage: + _1.serialize(buffer, boxed) case let _1 as Api.BusinessLocation: _1.serialize(buffer, boxed) case let _1 as Api.BusinessWeeklyOpen: @@ -1540,6 +1555,10 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.InputBotInlineResult: _1.serialize(buffer, boxed) + case let _1 as Api.InputBusinessAwayMessage: + _1.serialize(buffer, boxed) + case let _1 as Api.InputBusinessGreetingMessage: + _1.serialize(buffer, boxed) case let _1 as Api.InputChannel: _1.serialize(buffer, boxed) case let _1 as Api.InputChatPhoto: @@ -1594,6 +1613,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.InputPrivacyRule: _1.serialize(buffer, boxed) + case let _1 as Api.InputQuickReplyShortcut: + _1.serialize(buffer, boxed) case let _1 as Api.InputReplyTo: _1.serialize(buffer, boxed) case let _1 as Api.InputSecureFile: @@ -1764,6 +1785,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.PublicForward: _1.serialize(buffer, boxed) + case let _1 as Api.QuickReply: + _1.serialize(buffer, boxed) case let _1 as Api.Reaction: _1.serialize(buffer, boxed) case let _1 as Api.ReactionCount: @@ -2110,8 +2133,6 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.messages.QuickReplies: _1.serialize(buffer, boxed) - case let _1 as Api.messages.QuickReply: - _1.serialize(buffer, boxed) case let _1 as Api.messages.Reactions: _1.serialize(buffer, boxed) case let _1 as Api.messages.RecentStickers: diff --git a/submodules/TelegramApi/Sources/Api10.swift b/submodules/TelegramApi/Sources/Api10.swift index 7249151a22..4fe4c26f44 100644 --- a/submodules/TelegramApi/Sources/Api10.swift +++ b/submodules/TelegramApi/Sources/Api10.swift @@ -1,3 +1,59 @@ +public extension Api { + enum InputQuickReplyShortcut: TypeConstructorDescription { + case inputQuickReplyShortcut(shortcut: String) + case inputQuickReplyShortcutId(shortcutId: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputQuickReplyShortcut(let shortcut): + if boxed { + buffer.appendInt32(609840449) + } + serializeString(shortcut, buffer: buffer, boxed: false) + break + case .inputQuickReplyShortcutId(let shortcutId): + if boxed { + buffer.appendInt32(18418929) + } + serializeInt32(shortcutId, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputQuickReplyShortcut(let shortcut): + return ("inputQuickReplyShortcut", [("shortcut", shortcut as Any)]) + case .inputQuickReplyShortcutId(let shortcutId): + return ("inputQuickReplyShortcutId", [("shortcutId", shortcutId as Any)]) + } + } + + public static func parse_inputQuickReplyShortcut(_ reader: BufferReader) -> InputQuickReplyShortcut? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.InputQuickReplyShortcut.inputQuickReplyShortcut(shortcut: _1!) + } + else { + return nil + } + } + public static func parse_inputQuickReplyShortcutId(_ reader: BufferReader) -> InputQuickReplyShortcut? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.InputQuickReplyShortcut.inputQuickReplyShortcutId(shortcutId: _1!) + } + else { + return nil + } + } + + } +} public extension Api { indirect enum InputReplyTo: TypeConstructorDescription { case inputReplyToMessage(flags: Int32, replyToMsgId: Int32, topMsgId: Int32?, replyToPeerId: Api.InputPeer?, quoteText: String?, quoteEntities: [Api.MessageEntity]?, quoteOffset: Int32?) @@ -1014,83 +1070,3 @@ public extension Api { } } -public extension Api { - enum InputWallPaper: TypeConstructorDescription { - case inputWallPaper(id: Int64, accessHash: Int64) - case inputWallPaperNoFile(id: Int64) - case inputWallPaperSlug(slug: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputWallPaper(let id, let accessHash): - if boxed { - buffer.appendInt32(-433014407) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - break - case .inputWallPaperNoFile(let id): - if boxed { - buffer.appendInt32(-1770371538) - } - serializeInt64(id, buffer: buffer, boxed: false) - break - case .inputWallPaperSlug(let slug): - if boxed { - buffer.appendInt32(1913199744) - } - serializeString(slug, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputWallPaper(let id, let accessHash): - return ("inputWallPaper", [("id", id as Any), ("accessHash", accessHash as Any)]) - case .inputWallPaperNoFile(let id): - return ("inputWallPaperNoFile", [("id", id as Any)]) - case .inputWallPaperSlug(let slug): - return ("inputWallPaperSlug", [("slug", slug as Any)]) - } - } - - public static func parse_inputWallPaper(_ reader: BufferReader) -> InputWallPaper? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputWallPaper.inputWallPaper(id: _1!, accessHash: _2!) - } - else { - return nil - } - } - public static func parse_inputWallPaperNoFile(_ reader: BufferReader) -> InputWallPaper? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.InputWallPaper.inputWallPaperNoFile(id: _1!) - } - else { - return nil - } - } - public static func parse_inputWallPaperSlug(_ reader: BufferReader) -> InputWallPaper? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.InputWallPaper.inputWallPaperSlug(slug: _1!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api11.swift b/submodules/TelegramApi/Sources/Api11.swift index 666ea7ff21..89037ef849 100644 --- a/submodules/TelegramApi/Sources/Api11.swift +++ b/submodules/TelegramApi/Sources/Api11.swift @@ -1,3 +1,83 @@ +public extension Api { + enum InputWallPaper: TypeConstructorDescription { + case inputWallPaper(id: Int64, accessHash: Int64) + case inputWallPaperNoFile(id: Int64) + case inputWallPaperSlug(slug: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputWallPaper(let id, let accessHash): + if boxed { + buffer.appendInt32(-433014407) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + break + case .inputWallPaperNoFile(let id): + if boxed { + buffer.appendInt32(-1770371538) + } + serializeInt64(id, buffer: buffer, boxed: false) + break + case .inputWallPaperSlug(let slug): + if boxed { + buffer.appendInt32(1913199744) + } + serializeString(slug, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputWallPaper(let id, let accessHash): + return ("inputWallPaper", [("id", id as Any), ("accessHash", accessHash as Any)]) + case .inputWallPaperNoFile(let id): + return ("inputWallPaperNoFile", [("id", id as Any)]) + case .inputWallPaperSlug(let slug): + return ("inputWallPaperSlug", [("slug", slug as Any)]) + } + } + + public static func parse_inputWallPaper(_ reader: BufferReader) -> InputWallPaper? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputWallPaper.inputWallPaper(id: _1!, accessHash: _2!) + } + else { + return nil + } + } + public static func parse_inputWallPaperNoFile(_ reader: BufferReader) -> InputWallPaper? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.InputWallPaper.inputWallPaperNoFile(id: _1!) + } + else { + return nil + } + } + public static func parse_inputWallPaperSlug(_ reader: BufferReader) -> InputWallPaper? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.InputWallPaper.inputWallPaperSlug(slug: _1!) + } + else { + return nil + } + } + + } +} public extension Api { enum InputWebDocument: TypeConstructorDescription { case inputWebDocument(url: String, size: Int32, mimeType: String, attributes: [Api.DocumentAttribute]) @@ -904,165 +984,3 @@ public extension Api { } } -public extension Api { - enum LabeledPrice: TypeConstructorDescription { - case labeledPrice(label: String, amount: Int64) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .labeledPrice(let label, let amount): - if boxed { - buffer.appendInt32(-886477832) - } - serializeString(label, buffer: buffer, boxed: false) - serializeInt64(amount, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .labeledPrice(let label, let amount): - return ("labeledPrice", [("label", label as Any), ("amount", amount as Any)]) - } - } - - public static func parse_labeledPrice(_ reader: BufferReader) -> LabeledPrice? { - var _1: String? - _1 = parseString(reader) - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.LabeledPrice.labeledPrice(label: _1!, amount: _2!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum LangPackDifference: TypeConstructorDescription { - case langPackDifference(langCode: String, fromVersion: Int32, version: Int32, strings: [Api.LangPackString]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .langPackDifference(let langCode, let fromVersion, let version, let strings): - if boxed { - buffer.appendInt32(-209337866) - } - serializeString(langCode, buffer: buffer, boxed: false) - serializeInt32(fromVersion, buffer: buffer, boxed: false) - serializeInt32(version, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(strings.count)) - for item in strings { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .langPackDifference(let langCode, let fromVersion, let version, let strings): - return ("langPackDifference", [("langCode", langCode as Any), ("fromVersion", fromVersion as Any), ("version", version as Any), ("strings", strings as Any)]) - } - } - - public static func parse_langPackDifference(_ reader: BufferReader) -> LangPackDifference? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: [Api.LangPackString]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.LangPackString.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.LangPackDifference.langPackDifference(langCode: _1!, fromVersion: _2!, version: _3!, strings: _4!) - } - else { - return nil - } - } - - } -} -public extension Api { - enum LangPackLanguage: TypeConstructorDescription { - case langPackLanguage(flags: Int32, name: String, nativeName: String, langCode: String, baseLangCode: String?, pluralCode: String, stringsCount: Int32, translatedCount: Int32, translationsUrl: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .langPackLanguage(let flags, let name, let nativeName, let langCode, let baseLangCode, let pluralCode, let stringsCount, let translatedCount, let translationsUrl): - if boxed { - buffer.appendInt32(-288727837) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(name, buffer: buffer, boxed: false) - serializeString(nativeName, buffer: buffer, boxed: false) - serializeString(langCode, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeString(baseLangCode!, buffer: buffer, boxed: false)} - serializeString(pluralCode, buffer: buffer, boxed: false) - serializeInt32(stringsCount, buffer: buffer, boxed: false) - serializeInt32(translatedCount, buffer: buffer, boxed: false) - serializeString(translationsUrl, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .langPackLanguage(let flags, let name, let nativeName, let langCode, let baseLangCode, let pluralCode, let stringsCount, let translatedCount, let translationsUrl): - return ("langPackLanguage", [("flags", flags as Any), ("name", name as Any), ("nativeName", nativeName as Any), ("langCode", langCode as Any), ("baseLangCode", baseLangCode as Any), ("pluralCode", pluralCode as Any), ("stringsCount", stringsCount as Any), ("translatedCount", translatedCount as Any), ("translationsUrl", translationsUrl as Any)]) - } - } - - public static func parse_langPackLanguage(_ reader: BufferReader) -> LangPackLanguage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: String? - if Int(_1!) & Int(1 << 1) != 0 {_5 = parseString(reader) } - var _6: String? - _6 = parseString(reader) - var _7: Int32? - _7 = reader.readInt32() - var _8: Int32? - _8 = reader.readInt32() - var _9: String? - _9 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.LangPackLanguage.langPackLanguage(flags: _1!, name: _2!, nativeName: _3!, langCode: _4!, baseLangCode: _5, pluralCode: _6!, stringsCount: _7!, translatedCount: _8!, translationsUrl: _9!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api12.swift b/submodules/TelegramApi/Sources/Api12.swift index a4134abcf7..1edeff5668 100644 --- a/submodules/TelegramApi/Sources/Api12.swift +++ b/submodules/TelegramApi/Sources/Api12.swift @@ -1,3 +1,165 @@ +public extension Api { + enum LabeledPrice: TypeConstructorDescription { + case labeledPrice(label: String, amount: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .labeledPrice(let label, let amount): + if boxed { + buffer.appendInt32(-886477832) + } + serializeString(label, buffer: buffer, boxed: false) + serializeInt64(amount, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .labeledPrice(let label, let amount): + return ("labeledPrice", [("label", label as Any), ("amount", amount as Any)]) + } + } + + public static func parse_labeledPrice(_ reader: BufferReader) -> LabeledPrice? { + var _1: String? + _1 = parseString(reader) + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.LabeledPrice.labeledPrice(label: _1!, amount: _2!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum LangPackDifference: TypeConstructorDescription { + case langPackDifference(langCode: String, fromVersion: Int32, version: Int32, strings: [Api.LangPackString]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .langPackDifference(let langCode, let fromVersion, let version, let strings): + if boxed { + buffer.appendInt32(-209337866) + } + serializeString(langCode, buffer: buffer, boxed: false) + serializeInt32(fromVersion, buffer: buffer, boxed: false) + serializeInt32(version, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(strings.count)) + for item in strings { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .langPackDifference(let langCode, let fromVersion, let version, let strings): + return ("langPackDifference", [("langCode", langCode as Any), ("fromVersion", fromVersion as Any), ("version", version as Any), ("strings", strings as Any)]) + } + } + + public static func parse_langPackDifference(_ reader: BufferReader) -> LangPackDifference? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: [Api.LangPackString]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.LangPackString.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.LangPackDifference.langPackDifference(langCode: _1!, fromVersion: _2!, version: _3!, strings: _4!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum LangPackLanguage: TypeConstructorDescription { + case langPackLanguage(flags: Int32, name: String, nativeName: String, langCode: String, baseLangCode: String?, pluralCode: String, stringsCount: Int32, translatedCount: Int32, translationsUrl: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .langPackLanguage(let flags, let name, let nativeName, let langCode, let baseLangCode, let pluralCode, let stringsCount, let translatedCount, let translationsUrl): + if boxed { + buffer.appendInt32(-288727837) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(name, buffer: buffer, boxed: false) + serializeString(nativeName, buffer: buffer, boxed: false) + serializeString(langCode, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeString(baseLangCode!, buffer: buffer, boxed: false)} + serializeString(pluralCode, buffer: buffer, boxed: false) + serializeInt32(stringsCount, buffer: buffer, boxed: false) + serializeInt32(translatedCount, buffer: buffer, boxed: false) + serializeString(translationsUrl, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .langPackLanguage(let flags, let name, let nativeName, let langCode, let baseLangCode, let pluralCode, let stringsCount, let translatedCount, let translationsUrl): + return ("langPackLanguage", [("flags", flags as Any), ("name", name as Any), ("nativeName", nativeName as Any), ("langCode", langCode as Any), ("baseLangCode", baseLangCode as Any), ("pluralCode", pluralCode as Any), ("stringsCount", stringsCount as Any), ("translatedCount", translatedCount as Any), ("translationsUrl", translationsUrl as Any)]) + } + } + + public static func parse_langPackLanguage(_ reader: BufferReader) -> LangPackLanguage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: String? + if Int(_1!) & Int(1 << 1) != 0 {_5 = parseString(reader) } + var _6: String? + _6 = parseString(reader) + var _7: Int32? + _7 = reader.readInt32() + var _8: Int32? + _8 = reader.readInt32() + var _9: String? + _9 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.LangPackLanguage.langPackLanguage(flags: _1!, name: _2!, nativeName: _3!, langCode: _4!, baseLangCode: _5, pluralCode: _6!, stringsCount: _7!, translatedCount: _8!, translationsUrl: _9!) + } + else { + return nil + } + } + + } +} public extension Api { enum LangPackString: TypeConstructorDescription { case langPackString(key: String, value: String) diff --git a/submodules/TelegramApi/Sources/Api18.swift b/submodules/TelegramApi/Sources/Api18.swift index cce8c834d7..144d26302e 100644 --- a/submodules/TelegramApi/Sources/Api18.swift +++ b/submodules/TelegramApi/Sources/Api18.swift @@ -244,6 +244,54 @@ public extension Api { } } +public extension Api { + enum QuickReply: TypeConstructorDescription { + case quickReply(shortcutId: Int32, shortcut: String, topMessage: Int32, count: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .quickReply(let shortcutId, let shortcut, let topMessage, let count): + if boxed { + buffer.appendInt32(110563371) + } + serializeInt32(shortcutId, buffer: buffer, boxed: false) + serializeString(shortcut, buffer: buffer, boxed: false) + serializeInt32(topMessage, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .quickReply(let shortcutId, let shortcut, let topMessage, let count): + return ("quickReply", [("shortcutId", shortcutId as Any), ("shortcut", shortcut as Any), ("topMessage", topMessage as Any), ("count", count as Any)]) + } + } + + public static func parse_quickReply(_ reader: BufferReader) -> QuickReply? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.QuickReply.quickReply(shortcutId: _1!, shortcut: _2!, topMessage: _3!, count: _4!) + } + else { + return nil + } + } + + } +} public extension Api { enum Reaction: TypeConstructorDescription { case reactionCustomEmoji(documentId: Int64) diff --git a/submodules/TelegramApi/Sources/Api2.swift b/submodules/TelegramApi/Sources/Api2.swift index 4427b1efe7..302e1d26cc 100644 --- a/submodules/TelegramApi/Sources/Api2.swift +++ b/submodules/TelegramApi/Sources/Api2.swift @@ -589,16 +589,191 @@ public extension Api { } } public extension Api { - enum BusinessLocation: TypeConstructorDescription { - case businessLocation(geoPoint: Api.GeoPoint, address: String) + enum BusinessAwayMessage: TypeConstructorDescription { + case businessAwayMessage(flags: Int32, shortcutId: Int32, schedule: Api.BusinessAwayMessageSchedule, users: [Int64]?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .businessLocation(let geoPoint, let address): + case .businessAwayMessage(let flags, let shortcutId, let schedule, let users): if boxed { - buffer.appendInt32(-1104414653) + buffer.appendInt32(-1697809899) } - geoPoint.serialize(buffer, true) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(shortcutId, buffer: buffer, boxed: false) + schedule.serialize(buffer, true) + if Int(flags) & Int(1 << 4) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users!.count)) + for item in users! { + serializeInt64(item, buffer: buffer, boxed: false) + }} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .businessAwayMessage(let flags, let shortcutId, let schedule, let users): + return ("businessAwayMessage", [("flags", flags as Any), ("shortcutId", shortcutId as Any), ("schedule", schedule as Any), ("users", users as Any)]) + } + } + + public static func parse_businessAwayMessage(_ reader: BufferReader) -> BusinessAwayMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.BusinessAwayMessageSchedule? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.BusinessAwayMessageSchedule + } + var _4: [Int64]? + if Int(_1!) & Int(1 << 4) != 0 {if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.BusinessAwayMessage.businessAwayMessage(flags: _1!, shortcutId: _2!, schedule: _3!, users: _4) + } + else { + return nil + } + } + + } +} +public extension Api { + enum BusinessAwayMessageSchedule: TypeConstructorDescription { + case businessAwayMessageScheduleAlways + case businessAwayMessageScheduleCustom(startDate: Int32, endDate: Int32) + case businessAwayMessageScheduleOutsideWorkHours + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .businessAwayMessageScheduleAlways: + if boxed { + buffer.appendInt32(-910564679) + } + + break + case .businessAwayMessageScheduleCustom(let startDate, let endDate): + if boxed { + buffer.appendInt32(-867328308) + } + serializeInt32(startDate, buffer: buffer, boxed: false) + serializeInt32(endDate, buffer: buffer, boxed: false) + break + case .businessAwayMessageScheduleOutsideWorkHours: + if boxed { + buffer.appendInt32(-1007487743) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .businessAwayMessageScheduleAlways: + return ("businessAwayMessageScheduleAlways", []) + case .businessAwayMessageScheduleCustom(let startDate, let endDate): + return ("businessAwayMessageScheduleCustom", [("startDate", startDate as Any), ("endDate", endDate as Any)]) + case .businessAwayMessageScheduleOutsideWorkHours: + return ("businessAwayMessageScheduleOutsideWorkHours", []) + } + } + + public static func parse_businessAwayMessageScheduleAlways(_ reader: BufferReader) -> BusinessAwayMessageSchedule? { + return Api.BusinessAwayMessageSchedule.businessAwayMessageScheduleAlways + } + public static func parse_businessAwayMessageScheduleCustom(_ reader: BufferReader) -> BusinessAwayMessageSchedule? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.BusinessAwayMessageSchedule.businessAwayMessageScheduleCustom(startDate: _1!, endDate: _2!) + } + else { + return nil + } + } + public static func parse_businessAwayMessageScheduleOutsideWorkHours(_ reader: BufferReader) -> BusinessAwayMessageSchedule? { + return Api.BusinessAwayMessageSchedule.businessAwayMessageScheduleOutsideWorkHours + } + + } +} +public extension Api { + enum BusinessGreetingMessage: TypeConstructorDescription { + case businessGreetingMessage(flags: Int32, shortcutId: Int32, users: [Int64]?, noActivityDays: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .businessGreetingMessage(let flags, let shortcutId, let users, let noActivityDays): + if boxed { + buffer.appendInt32(-1600596660) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(shortcutId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 4) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users!.count)) + for item in users! { + serializeInt64(item, buffer: buffer, boxed: false) + }} + serializeInt32(noActivityDays, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .businessGreetingMessage(let flags, let shortcutId, let users, let noActivityDays): + return ("businessGreetingMessage", [("flags", flags as Any), ("shortcutId", shortcutId as Any), ("users", users as Any), ("noActivityDays", noActivityDays as Any)]) + } + } + + public static func parse_businessGreetingMessage(_ reader: BufferReader) -> BusinessGreetingMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Int64]? + if Int(_1!) & Int(1 << 4) != 0 {if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 4) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.BusinessGreetingMessage.businessGreetingMessage(flags: _1!, shortcutId: _2!, users: _3, noActivityDays: _4!) + } + else { + return nil + } + } + + } +} +public extension Api { + enum BusinessLocation: TypeConstructorDescription { + case businessLocation(flags: Int32, geoPoint: Api.GeoPoint?, address: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .businessLocation(let flags, let geoPoint, let address): + if boxed { + buffer.appendInt32(-1403249929) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {geoPoint!.serialize(buffer, true)} serializeString(address, buffer: buffer, boxed: false) break } @@ -606,22 +781,25 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .businessLocation(let geoPoint, let address): - return ("businessLocation", [("geoPoint", geoPoint as Any), ("address", address as Any)]) + case .businessLocation(let flags, let geoPoint, let address): + return ("businessLocation", [("flags", flags as Any), ("geoPoint", geoPoint as Any), ("address", address as Any)]) } } public static func parse_businessLocation(_ reader: BufferReader) -> BusinessLocation? { - var _1: Api.GeoPoint? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.GeoPoint - } - var _2: String? - _2 = parseString(reader) + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.GeoPoint? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.GeoPoint + } } + var _3: String? + _3 = parseString(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.BusinessLocation.businessLocation(geoPoint: _1!, address: _2!) + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.BusinessLocation.businessLocation(flags: _1!, geoPoint: _2, address: _3!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api22.swift b/submodules/TelegramApi/Sources/Api22.swift index b4715fe70e..1d2611a4b9 100644 --- a/submodules/TelegramApi/Sources/Api22.swift +++ b/submodules/TelegramApi/Sources/Api22.swift @@ -582,7 +582,7 @@ public extension Api { case updateNewChannelMessage(message: Api.Message, pts: Int32, ptsCount: Int32) case updateNewEncryptedMessage(message: Api.EncryptedMessage, qts: Int32) case updateNewMessage(message: Api.Message, pts: Int32, ptsCount: Int32) - case updateNewQuickReply(quickReply: Api.messages.QuickReply) + case updateNewQuickReply(quickReply: Api.QuickReply) case updateNewScheduledMessage(message: Api.Message) case updateNewStickerSet(stickerset: Api.messages.StickerSet) case updateNotifySettings(peer: Api.NotifyPeer, notifySettings: Api.PeerNotifySettings) @@ -600,7 +600,7 @@ public extension Api { case updatePinnedSavedDialogs(flags: Int32, order: [Api.DialogPeer]?) case updatePrivacy(key: Api.PrivacyKey, rules: [Api.PrivacyRule]) case updatePtsChanged - case updateQuickReplies(quickReplies: [Api.messages.QuickReply]) + case updateQuickReplies(quickReplies: [Api.QuickReply]) case updateQuickReplyMessage(message: Api.Message) case updateReadChannelDiscussionInbox(flags: Int32, channelId: Int64, topMsgId: Int32, readMaxId: Int32, broadcastId: Int64?, broadcastPost: Int32?) case updateReadChannelDiscussionOutbox(channelId: Int64, topMsgId: Int32, readMaxId: Int32) @@ -1320,7 +1320,7 @@ public extension Api { break case .updateNewQuickReply(let quickReply): if boxed { - buffer.appendInt32(-1386034803) + buffer.appendInt32(-180508905) } quickReply.serialize(buffer, true) break @@ -1478,7 +1478,7 @@ public extension Api { break case .updateQuickReplies(let quickReplies): if boxed { - buffer.appendInt32(230929261) + buffer.appendInt32(-112784718) } buffer.appendInt32(481674261) buffer.appendInt32(Int32(quickReplies.count)) @@ -3456,9 +3456,9 @@ public extension Api { } } public static func parse_updateNewQuickReply(_ reader: BufferReader) -> Update? { - var _1: Api.messages.QuickReply? + var _1: Api.QuickReply? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.messages.QuickReply + _1 = Api.parse(reader, signature: signature) as? Api.QuickReply } let _c1 = _1 != nil if _c1 { @@ -3756,9 +3756,9 @@ public extension Api { return Api.Update.updatePtsChanged } public static func parse_updateQuickReplies(_ reader: BufferReader) -> Update? { - var _1: [Api.messages.QuickReply]? + var _1: [Api.QuickReply]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.messages.QuickReply.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.QuickReply.self) } let _c1 = _1 != nil if _c1 { diff --git a/submodules/TelegramApi/Sources/Api23.swift b/submodules/TelegramApi/Sources/Api23.swift index 320cf74bfb..12f4890d7c 100644 --- a/submodules/TelegramApi/Sources/Api23.swift +++ b/submodules/TelegramApi/Sources/Api23.swift @@ -602,13 +602,13 @@ public extension Api { } public extension Api { enum UserFull: TypeConstructorDescription { - case userFull(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, themeEmoticon: String?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, premiumGifts: [Api.PremiumGiftOption]?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?) + case userFull(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, themeEmoticon: String?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, premiumGifts: [Api.PremiumGiftOption]?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts, let wallpaper, let stories, let businessWorkHours, let businessLocation): + case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage): if boxed { - buffer.appendInt32(-501688336) + buffer.appendInt32(587153029) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags2, buffer: buffer, boxed: false) @@ -637,14 +637,16 @@ public extension Api { if Int(flags) & Int(1 << 25) != 0 {stories!.serialize(buffer, true)} if Int(flags2) & Int(1 << 0) != 0 {businessWorkHours!.serialize(buffer, true)} if Int(flags2) & Int(1 << 1) != 0 {businessLocation!.serialize(buffer, true)} + if Int(flags2) & Int(1 << 2) != 0 {businessGreetingMessage!.serialize(buffer, true)} + if Int(flags2) & Int(1 << 3) != 0 {businessAwayMessage!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts, let wallpaper, let stories, let businessWorkHours, let businessLocation): - return ("userFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("settings", settings as Any), ("personalPhoto", personalPhoto as Any), ("profilePhoto", profilePhoto as Any), ("fallbackPhoto", fallbackPhoto as Any), ("notifySettings", notifySettings as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("commonChatsCount", commonChatsCount as Any), ("folderId", folderId as Any), ("ttlPeriod", ttlPeriod as Any), ("themeEmoticon", themeEmoticon as Any), ("privateForwardName", privateForwardName as Any), ("botGroupAdminRights", botGroupAdminRights as Any), ("botBroadcastAdminRights", botBroadcastAdminRights as Any), ("premiumGifts", premiumGifts as Any), ("wallpaper", wallpaper as Any), ("stories", stories as Any), ("businessWorkHours", businessWorkHours as Any), ("businessLocation", businessLocation as Any)]) + case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage): + return ("userFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("settings", settings as Any), ("personalPhoto", personalPhoto as Any), ("profilePhoto", profilePhoto as Any), ("fallbackPhoto", fallbackPhoto as Any), ("notifySettings", notifySettings as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("commonChatsCount", commonChatsCount as Any), ("folderId", folderId as Any), ("ttlPeriod", ttlPeriod as Any), ("themeEmoticon", themeEmoticon as Any), ("privateForwardName", privateForwardName as Any), ("botGroupAdminRights", botGroupAdminRights as Any), ("botBroadcastAdminRights", botBroadcastAdminRights as Any), ("premiumGifts", premiumGifts as Any), ("wallpaper", wallpaper as Any), ("stories", stories as Any), ("businessWorkHours", businessWorkHours as Any), ("businessLocation", businessLocation as Any), ("businessGreetingMessage", businessGreetingMessage as Any), ("businessAwayMessage", businessAwayMessage as Any)]) } } @@ -721,6 +723,14 @@ public extension Api { if Int(_2!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { _23 = Api.parse(reader, signature: signature) as? Api.BusinessLocation } } + var _24: Api.BusinessGreetingMessage? + if Int(_2!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _24 = Api.parse(reader, signature: signature) as? Api.BusinessGreetingMessage + } } + var _25: Api.BusinessAwayMessage? + if Int(_2!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _25 = Api.parse(reader, signature: signature) as? Api.BusinessAwayMessage + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -744,8 +754,10 @@ public extension Api { let _c21 = (Int(_1!) & Int(1 << 25) == 0) || _21 != nil let _c22 = (Int(_2!) & Int(1 << 0) == 0) || _22 != nil let _c23 = (Int(_2!) & Int(1 << 1) == 0) || _23 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 { - return Api.UserFull.userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, themeEmoticon: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, premiumGifts: _19, wallpaper: _20, stories: _21, businessWorkHours: _22, businessLocation: _23) + let _c24 = (Int(_2!) & Int(1 << 2) == 0) || _24 != nil + let _c25 = (Int(_2!) & Int(1 << 3) == 0) || _25 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 { + return Api.UserFull.userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, themeEmoticon: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, premiumGifts: _19, wallpaper: _20, stories: _21, businessWorkHours: _22, businessLocation: _23, businessGreetingMessage: _24, businessAwayMessage: _25) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api28.swift b/submodules/TelegramApi/Sources/Api28.swift index 4b1dfe924a..30748cf3c0 100644 --- a/submodules/TelegramApi/Sources/Api28.swift +++ b/submodules/TelegramApi/Sources/Api28.swift @@ -1290,14 +1290,14 @@ public extension Api.messages { } public extension Api.messages { enum QuickReplies: TypeConstructorDescription { - case quickReplies(quickReplies: [Api.messages.QuickReply], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) + case quickReplies(quickReplies: [Api.QuickReply], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) case quickRepliesNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .quickReplies(let quickReplies, let messages, let chats, let users): if boxed { - buffer.appendInt32(2094438528) + buffer.appendInt32(-963811691) } buffer.appendInt32(481674261) buffer.appendInt32(Int32(quickReplies.count)) @@ -1339,9 +1339,9 @@ public extension Api.messages { } public static func parse_quickReplies(_ reader: BufferReader) -> QuickReplies? { - var _1: [Api.messages.QuickReply]? + var _1: [Api.QuickReply]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.messages.QuickReply.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.QuickReply.self) } var _2: [Api.Message]? if let _ = reader.readInt32() { @@ -1372,54 +1372,6 @@ public extension Api.messages { } } -public extension Api.messages { - enum QuickReply: TypeConstructorDescription { - case quickReply(shortcutId: Int32, shortcut: String, topMessage: Int32, count: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .quickReply(let shortcutId, let shortcut, let topMessage, let count): - if boxed { - buffer.appendInt32(-1810973582) - } - serializeInt32(shortcutId, buffer: buffer, boxed: false) - serializeString(shortcut, buffer: buffer, boxed: false) - serializeInt32(topMessage, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .quickReply(let shortcutId, let shortcut, let topMessage, let count): - return ("quickReply", [("shortcutId", shortcutId as Any), ("shortcut", shortcut as Any), ("topMessage", topMessage as Any), ("count", count as Any)]) - } - } - - public static func parse_quickReply(_ reader: BufferReader) -> QuickReply? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.QuickReply.quickReply(shortcutId: _1!, shortcut: _2!, topMessage: _3!, count: _4!) - } - else { - return nil - } - } - - } -} public extension Api.messages { enum Reactions: TypeConstructorDescription { case reactions(hash: Int64, reactions: [Api.Reaction]) diff --git a/submodules/TelegramApi/Sources/Api32.swift b/submodules/TelegramApi/Sources/Api32.swift index d8338e5bd5..55cab5fd70 100644 --- a/submodules/TelegramApi/Sources/Api32.swift +++ b/submodules/TelegramApi/Sources/Api32.swift @@ -1261,12 +1261,44 @@ public extension Api.functions.account { }) } } +public extension Api.functions.account { + static func updateBusinessAwayMessage(flags: Int32, message: Api.InputBusinessAwayMessage?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1570078811) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {message!.serialize(buffer, true)} + return (FunctionDescription(name: "account.updateBusinessAwayMessage", parameters: [("flags", String(describing: flags)), ("message", String(describing: message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateBusinessGreetingMessage(flags: Int32, message: Api.InputBusinessGreetingMessage?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1724755908) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {message!.serialize(buffer, true)} + return (FunctionDescription(name: "account.updateBusinessGreetingMessage", parameters: [("flags", String(describing: flags)), ("message", String(describing: message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.account { static func updateBusinessLocation(flags: Int32, geoPoint: Api.InputGeoPoint?, address: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1040005974) + buffer.appendInt32(-1637149926) serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {geoPoint!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {geoPoint!.serialize(buffer, true)} if Int(flags) & Int(1 << 0) != 0 {serializeString(address!, buffer: buffer, boxed: false)} return (FunctionDescription(name: "account.updateBusinessLocation", parameters: [("flags", String(describing: flags)), ("geoPoint", String(describing: geoPoint)), ("address", String(describing: address))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in let reader = BufferReader(buffer) @@ -4645,6 +4677,21 @@ public extension Api.functions.messages { }) } } +public extension Api.functions.messages { + static func deleteQuickReplyShortcut(shortcutId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1019234112) + serializeInt32(shortcutId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.deleteQuickReplyShortcut", parameters: [("shortcutId", String(describing: shortcutId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.messages { static func deleteRevokedExportedChatInvites(peer: Api.InputPeer, adminId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -4921,9 +4968,9 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, topMsgId: Int32?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, topMsgId: Int32?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-709978674) + buffer.appendInt32(-721186296) serializeInt32(flags, buffer: buffer, boxed: false) fromPeer.serialize(buffer, true) buffer.appendInt32(481674261) @@ -4940,7 +4987,7 @@ public extension Api.functions.messages { if Int(flags) & Int(1 << 9) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - if Int(flags) & Int(1 << 17) != 0 {serializeString(quickReplyShortcut!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", String(describing: flags)), ("fromPeer", String(describing: fromPeer)), ("id", String(describing: id)), ("randomId", String(describing: randomId)), ("toPeer", String(describing: toPeer)), ("topMsgId", String(describing: topMsgId)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? @@ -7186,9 +7233,9 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func sendInlineBotResult(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, randomId: Int64, queryId: Int64, id: String, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func sendInlineBotResult(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, randomId: Int64, queryId: Int64, id: String, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-407001673) + buffer.appendInt32(1052698730) serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} @@ -7197,7 +7244,7 @@ public extension Api.functions.messages { serializeString(id, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - if Int(flags) & Int(1 << 17) != 0 {serializeString(quickReplyShortcut!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} return (FunctionDescription(name: "messages.sendInlineBotResult", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("randomId", String(describing: randomId)), ("queryId", String(describing: queryId)), ("id", String(describing: id)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? @@ -7209,9 +7256,9 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func sendMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, media: Api.InputMedia, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func sendMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, media: Api.InputMedia, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-10487971) + buffer.appendInt32(2077646913) serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} @@ -7226,7 +7273,7 @@ public extension Api.functions.messages { }} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - if Int(flags) & Int(1 << 17) != 0 {serializeString(quickReplyShortcut!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} return (FunctionDescription(name: "messages.sendMedia", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("media", String(describing: media)), ("message", String(describing: message)), ("randomId", String(describing: randomId)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? @@ -7238,9 +7285,9 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func sendMessage(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func sendMessage(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1750387040) + buffer.appendInt32(-537394132) serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} @@ -7254,7 +7301,7 @@ public extension Api.functions.messages { }} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - if Int(flags) & Int(1 << 17) != 0 {serializeString(quickReplyShortcut!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} return (FunctionDescription(name: "messages.sendMessage", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("message", String(describing: message)), ("randomId", String(describing: randomId)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? @@ -7266,9 +7313,9 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func sendMultiMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, multiMedia: [Api.InputSingleMedia], scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func sendMultiMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, multiMedia: [Api.InputSingleMedia], scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-2027543192) + buffer.appendInt32(211175177) serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} @@ -7279,7 +7326,7 @@ public extension Api.functions.messages { } if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - if Int(flags) & Int(1 << 17) != 0 {serializeString(quickReplyShortcut!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} return (FunctionDescription(name: "messages.sendMultiMedia", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("multiMedia", String(describing: multiMedia)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? diff --git a/submodules/TelegramApi/Sources/Api7.swift b/submodules/TelegramApi/Sources/Api7.swift index 80bc038f38..16de0844ae 100644 --- a/submodules/TelegramApi/Sources/Api7.swift +++ b/submodules/TelegramApi/Sources/Api7.swift @@ -262,6 +262,116 @@ public extension Api { } } +public extension Api { + enum InputBusinessAwayMessage: TypeConstructorDescription { + case inputBusinessAwayMessage(flags: Int32, shortcutId: Int32, schedule: Api.BusinessAwayMessageSchedule, users: [Api.InputUser]?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputBusinessAwayMessage(let flags, let shortcutId, let schedule, let users): + if boxed { + buffer.appendInt32(-831530424) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(shortcutId, buffer: buffer, boxed: false) + schedule.serialize(buffer, true) + if Int(flags) & Int(1 << 4) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users!.count)) + for item in users! { + item.serialize(buffer, true) + }} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputBusinessAwayMessage(let flags, let shortcutId, let schedule, let users): + return ("inputBusinessAwayMessage", [("flags", flags as Any), ("shortcutId", shortcutId as Any), ("schedule", schedule as Any), ("users", users as Any)]) + } + } + + public static func parse_inputBusinessAwayMessage(_ reader: BufferReader) -> InputBusinessAwayMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.BusinessAwayMessageSchedule? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.BusinessAwayMessageSchedule + } + var _4: [Api.InputUser]? + if Int(_1!) & Int(1 << 4) != 0 {if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputBusinessAwayMessage.inputBusinessAwayMessage(flags: _1!, shortcutId: _2!, schedule: _3!, users: _4) + } + else { + return nil + } + } + + } +} +public extension Api { + enum InputBusinessGreetingMessage: TypeConstructorDescription { + case inputBusinessGreetingMessage(flags: Int32, shortcutId: Int32, users: [Api.InputUser]?, noActivityDays: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputBusinessGreetingMessage(let flags, let shortcutId, let users, let noActivityDays): + if boxed { + buffer.appendInt32(2102015497) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(shortcutId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 4) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users!.count)) + for item in users! { + item.serialize(buffer, true) + }} + serializeInt32(noActivityDays, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputBusinessGreetingMessage(let flags, let shortcutId, let users, let noActivityDays): + return ("inputBusinessGreetingMessage", [("flags", flags as Any), ("shortcutId", shortcutId as Any), ("users", users as Any), ("noActivityDays", noActivityDays as Any)]) + } + } + + public static func parse_inputBusinessGreetingMessage(_ reader: BufferReader) -> InputBusinessGreetingMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.InputUser]? + if Int(_1!) & Int(1 << 4) != 0 {if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self) + } } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 4) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputBusinessGreetingMessage.inputBusinessGreetingMessage(flags: _1!, shortcutId: _2!, users: _3, noActivityDays: _4!) + } + else { + return nil + } + } + + } +} public extension Api { indirect enum InputChannel: TypeConstructorDescription { case inputChannel(channelId: Int64, accessHash: Int64) diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index 0d1df5f992..79c3c4658a 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -62,6 +62,7 @@ enum AccountStateGlobalNotificationSettingsSubject { enum AccountStateMutationOperation { case AddMessages([StoreMessage], AddMessagesLocation) case AddScheduledMessages([StoreMessage]) + case AddQuickReplyMessages([StoreMessage]) case DeleteMessagesWithGlobalIds([Int32]) case DeleteMessages([MessageId]) case EditMessage(MessageId, StoreMessage) @@ -332,6 +333,10 @@ struct AccountMutableState { self.addOperation(.AddScheduledMessages(messages)) } + mutating func addQuickReplyMessages(_ messages: [StoreMessage]) { + self.addOperation(.AddQuickReplyMessages(messages)) + } + mutating func addDisplayAlert(_ text: String, isDropAuth: Bool) { self.displayAlerts.append((text: text, isDropAuth: isDropAuth)) } @@ -709,6 +714,19 @@ struct AccountMutableState { } } } + case let .AddQuickReplyMessages(messages): + for message in messages { + if case let .Id(id) = message.id { + self.storedMessages.insert(id) + inner: for attribute in message.attributes { + if let attribute = attribute as? ReplyMessageAttribute { + self.referencedReplyMessageIds.add(sourceId: id, targetId: attribute.messageId) + } else if let attribute = attribute as? ReplyStoryAttribute { + self.referencedStoryIds.insert(attribute.storyId) + } + } + } + } case let .UpdateState(state): self.state = state case let .UpdateChannelState(peerId, pts): diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index 84789814ab..c70bf2b60f 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -208,6 +208,7 @@ private var declaredEncodables: Void = { declareEncodable(WebpagePreviewMessageAttribute.self, f: { WebpagePreviewMessageAttribute(decoder: $0) }) declareEncodable(DerivedDataMessageAttribute.self, f: { DerivedDataMessageAttribute(decoder: $0) }) declareEncodable(TelegramApplicationIcons.self, f: { TelegramApplicationIcons(decoder: $0) }) + declareEncodable(OutgoingQuickReplyMessageAttribute.self, f: { OutgoingQuickReplyMessageAttribute(decoder: $0) }) return }() diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 7829c5dcd7..5ecd19cfc1 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -597,8 +597,13 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes extension StoreMessage { convenience init?(apiMessage: Api.Message, accountPeerId: PeerId, peerIsForum: Bool, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { switch apiMessage { - case let .message(flags, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, _): + case let .message(flags, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId): let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId + + var namespace = namespace + if quickReplyShortcutId != nil { + namespace = Namespaces.Message.QuickReplyCloud + } let peerId: PeerId var authorId: PeerId? @@ -663,7 +668,7 @@ extension StoreMessage { threadId = Int64(threadIdValue.id) } } - attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId, quote: quote, isQuote: isQuote)) + attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: namespace, id: replyToMsgId), threadMessageId: threadMessageId, quote: quote, isQuote: isQuote)) } if let replyHeader = replyHeader { attributes.append(QuotedReplyMessageAttribute(apiHeader: replyHeader, quote: quote, isQuote: isQuote)) @@ -729,6 +734,10 @@ extension StoreMessage { if peerId == accountPeerId, let savedPeerId = savedPeerId { threadId = savedPeerId.peerId.toInt64() } + + if let quickReplyShortcutId { + threadId = Int64(quickReplyShortcutId) + } let messageText = message var medias: [Media] = [] @@ -791,7 +800,7 @@ extension StoreMessage { attributes.append(InlineBotMessageAttribute(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(viaBotId)), title: nil)) } - if namespace != Namespaces.Message.ScheduledCloud { + if namespace != Namespaces.Message.ScheduledCloud && namespace != Namespaces.Message.QuickReplyCloud { if let views = views { attributes.append(ViewCountMessageAttribute(count: Int(views))) } diff --git a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift index 4c23fe9516..2b70d10d63 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift @@ -138,6 +138,15 @@ public enum EnqueueMessage { } } + public func withUpdatedThreadId(_ threadId: Int64?) -> EnqueueMessage { + switch self { + case let .message(text, attributes, inlineStickers, mediaReference, _, replyToMessageId, replyToStoryId, localGroupingKey, correlationId, bubbleUpEmojiOrStickersets): + return .message(text: text, attributes: attributes, inlineStickers: inlineStickers, mediaReference: mediaReference, threadId: threadId, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, localGroupingKey: localGroupingKey, correlationId: correlationId, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets) + case let .forward(source, _, grouping, attributes, correlationId): + return .forward(source: source, threadId: threadId, grouping: grouping, attributes: attributes, correlationId: correlationId) + } + } + public var groupingKey: Int64? { if case let .message(_, _, _, _, _, _, _, localGroupingKey, _, _) = self { return localGroupingKey @@ -225,6 +234,8 @@ private func filterMessageAttributesForOutgoingMessage(_ attributes: [MessageAtt return true case _ as OutgoingScheduleInfoMessageAttribute: return true + case _ as OutgoingQuickReplyMessageAttribute: + return true case _ as EmbeddedMediaStickersMessageAttribute: return true case _ as EmojiSearchQueryMessageAttribute: @@ -254,6 +265,8 @@ private func filterMessageAttributesForForwardedMessage(_ attributes: [MessageAt return true case _ as OutgoingScheduleInfoMessageAttribute: return true + case _ as OutgoingQuickReplyMessageAttribute: + return true case _ as ForwardOptionsMessageAttribute: return true case _ as SendAsMessageAttribute: @@ -673,6 +686,9 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, messageNamespace = Namespaces.Message.ScheduledLocal effectiveTimestamp = attribute.scheduleTime } + } else if attribute is OutgoingQuickReplyMessageAttribute { + messageNamespace = Namespaces.Message.QuickReplyLocal + effectiveTimestamp = 0 } else if let attribute = attribute as? SendAsMessageAttribute { if let peer = transaction.getPeer(attribute.peerId) { sendAsPeer = peer @@ -698,11 +714,14 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, if messageNamespace != Namespaces.Message.ScheduledLocal { attributes.removeAll(where: { $0 is OutgoingScheduleInfoMessageAttribute }) } + if messageNamespace != Namespaces.Message.QuickReplyLocal { + attributes.removeAll(where: { $0 is OutgoingQuickReplyMessageAttribute }) + } if let peer = peer as? TelegramChannel { switch peer.info { case let .broadcast(info): - if messageNamespace != Namespaces.Message.ScheduledLocal { + if messageNamespace != Namespaces.Message.ScheduledLocal && messageNamespace != Namespaces.Message.QuickReplyLocal { attributes.append(ViewCountMessageAttribute(count: 1)) } if info.flags.contains(.messagesShouldHaveSignatures) { @@ -911,6 +930,9 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, messageNamespace = Namespaces.Message.ScheduledLocal effectiveTimestamp = attribute.scheduleTime } + } else if attribute is OutgoingQuickReplyMessageAttribute { + messageNamespace = Namespaces.Message.QuickReplyLocal + effectiveTimestamp = 0 } else if let attribute = attribute as? ReplyMessageAttribute { if let threadMessageId = attribute.threadMessageId { threadId = Int64(threadMessageId.id) @@ -940,6 +962,9 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, if messageNamespace != Namespaces.Message.ScheduledLocal { attributes.removeAll(where: { $0 is OutgoingScheduleInfoMessageAttribute }) } + if messageNamespace != Namespaces.Message.QuickReplyLocal { + attributes.removeAll(where: { $0 is OutgoingQuickReplyMessageAttribute }) + } let (tags, globalTags) = tagsForStoreMessage(incoming: false, attributes: attributes, media: sourceMessage.media, textEntities: entitiesAttribute?.entities, isPinned: false) diff --git a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift index e568ed9c78..8744792086 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift @@ -174,7 +174,13 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, } } - return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: text, media: inputMedia, replyMarkup: nil, entities: apiEntities, scheduleDate: effectiveScheduleTime, quickReplyShortcutId: nil)) + var quickReplyShortcutId: Int32? + if messageId.namespace == Namespaces.Message.QuickReplyCloud { + quickReplyShortcutId = Int32(clamping: message.threadId ?? 0) + flags |= Int32(1 << 17) + } + + return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: text, media: inputMedia, replyMarkup: nil, entities: apiEntities, scheduleDate: effectiveScheduleTime, quickReplyShortcutId: quickReplyShortcutId)) |> map { result -> Api.Updates? in return result } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index a68c802ace..7dfad231f5 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -1658,12 +1658,26 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: if let message = StoreMessage(apiMessage: apiMessage, accountPeerId: accountPeerId, peerIsForum: peerIsForum, namespace: Namespaces.Message.ScheduledCloud) { updatedState.addScheduledMessages([message]) } + case let .updateQuickReplyMessage(apiMessage): + var peerIsForum = false + if let peerId = apiMessage.peerId { + peerIsForum = updatedState.isPeerForum(peerId: peerId) + } + if let message = StoreMessage(apiMessage: apiMessage, accountPeerId: accountPeerId, peerIsForum: peerIsForum, namespace: Namespaces.Message.QuickReplyCloud) { + updatedState.addQuickReplyMessages([message]) + } case let .updateDeleteScheduledMessages(peer, messages): var messageIds: [MessageId] = [] for message in messages { messageIds.append(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.ScheduledCloud, id: message)) } updatedState.deleteMessages(messageIds) + case let .updateDeleteQuickReplyMessages(_, messages): + var messageIds: [MessageId] = [] + for message in messages { + messageIds.append(MessageId(peerId: accountPeerId, namespace: Namespaces.Message.QuickReplyCloud, id: message)) + } + updatedState.deleteMessages(messageIds) case let .updateTheme(theme): updatedState.updateTheme(TelegramTheme(apiTheme: theme)) case let .updateMessageID(id, randomId): @@ -3250,6 +3264,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) var currentAddMessages: OptimizeAddMessagesState? var currentAddScheduledMessages: OptimizeAddMessagesState? + var currentAddQuickReplyMessages: OptimizeAddMessagesState? for operation in operations { switch operation { case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper: @@ -3259,6 +3274,9 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) if let currentAddScheduledMessages = currentAddScheduledMessages, !currentAddScheduledMessages.messages.isEmpty { result.append(.AddScheduledMessages(currentAddScheduledMessages.messages)) } + if let currentAddQuickReplyMessages = currentAddQuickReplyMessages, !currentAddQuickReplyMessages.messages.isEmpty { + result.append(.AddQuickReplyMessages(currentAddQuickReplyMessages.messages)) + } currentAddMessages = nil result.append(operation) case let .UpdateState(state): @@ -3284,6 +3302,12 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) } else { currentAddScheduledMessages = OptimizeAddMessagesState(messages: messages, location: .Random) } + case let .AddQuickReplyMessages(messages): + if let currentAddQuickReplyMessages = currentAddQuickReplyMessages { + currentAddQuickReplyMessages.messages.append(contentsOf: messages) + } else { + currentAddQuickReplyMessages = OptimizeAddMessagesState(messages: messages, location: .Random) + } } } if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { @@ -3294,6 +3318,10 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) result.append(.AddScheduledMessages(currentAddScheduledMessages.messages)) } + if let currentAddQuickReplyMessages = currentAddQuickReplyMessages, !currentAddQuickReplyMessages.messages.isEmpty { + result.append(.AddQuickReplyMessages(currentAddQuickReplyMessages.messages)) + } + if let updatedState = updatedState { result.append(.UpdateState(updatedState)) } @@ -3745,6 +3773,16 @@ func replayFinalState( let _ = transaction.addMessages(messages, location: .Random) } } + case let .AddQuickReplyMessages(messages): + for message in messages { + if case let .Id(id) = message.id, let _ = transaction.getMessage(id) { + transaction.updateMessage(id) { _ -> PostboxUpdateMessage in + return .update(message) + } + } else { + let _ = transaction.addMessages(messages, location: .Random) + } + } case let .DeleteMessagesWithGlobalIds(ids): var resourceIds: [MediaResourceId] = [] transaction.deleteMessagesWithGlobalIds(ids, forEachMedia: { media in diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index 50e1d1d148..330d2ab0b3 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -61,16 +61,30 @@ private func pollMessages(entries: [MessageHistoryEntry]) -> (Set, [M return (messageIds, messages) } -private func fetchWebpage(account: Account, messageId: MessageId) -> Signal { +private func fetchWebpage(account: Account, messageId: MessageId, threadId: Int64?) -> Signal { let accountPeerId = account.peerId return account.postbox.loadedPeerWithId(messageId.peerId) |> take(1) |> mapToSignal { peer in if let inputPeer = apiInputPeer(peer) { - let isScheduledMessage = Namespaces.Message.allScheduled.contains(messageId.namespace) + let targetMessageNamespace: MessageId.Namespace + if Namespaces.Message.allScheduled.contains(messageId.namespace) { + targetMessageNamespace = Namespaces.Message.ScheduledCloud + } else if Namespaces.Message.allQuickReply.contains(messageId.namespace) { + targetMessageNamespace = Namespaces.Message.QuickReplyCloud + } else { + targetMessageNamespace = Namespaces.Message.Cloud + } + let messages: Signal - if isScheduledMessage { + if Namespaces.Message.allScheduled.contains(messageId.namespace) { messages = account.network.request(Api.functions.messages.getScheduledMessages(peer: inputPeer, id: [messageId.id])) + } else if Namespaces.Message.allQuickReply.contains(messageId.namespace) { + if let threadId { + messages = account.network.request(Api.functions.messages.getQuickReplyMessages(flags: 1 << 0, shortcutId: Int32(clamping: threadId), id: [messageId.id], hash: 0)) + } else { + messages = .never() + } } else { switch inputPeer { case let .inputPeerChannel(channelId, accessHash): @@ -109,7 +123,7 @@ private func fetchWebpage(account: Account, messageId: MessageId) -> Signal() - private var updatedUnsupportedMediaMessageIdsAndTimestamps: [MessageId: Int32] = [:] + private var updatedUnsupportedMediaMessageIdsAndTimestamps: [MessageAndThreadId: Int32] = [:] private var refreshSecretChatMediaMessageIdsAndTimestamps: [MessageId: Int32] = [:] private var refreshStoriesForMessageIdsAndTimestamps: [MessageId: Int32] = [:] private var nextUpdatedUnsupportedMediaDisposableId: Int32 = 0 @@ -367,7 +381,7 @@ public final class AccountViewTracker { } } - private func updatePendingWebpages(viewId: Int32, messageIds: Set, localWebpages: [MessageId: (MediaId, String)]) { + private func updatePendingWebpages(viewId: Int32, threadId: Int64?, messageIds: Set, localWebpages: [MessageId: (MediaId, String)]) { self.queue.async { var addedMessageIds: [MessageId] = [] var removedMessageIds: [MessageId] = [] @@ -440,7 +454,7 @@ public final class AccountViewTracker { } }) } else if messageId.namespace == Namespaces.Message.Cloud { - self.webpageDisposables[messageId] = fetchWebpage(account: account, messageId: messageId).start(completed: { [weak self] in + self.webpageDisposables[messageId] = fetchWebpage(account: account, messageId: messageId, threadId: threadId).start(completed: { [weak self] in if let strongSelf = self { strongSelf.queue.async { strongSelf.webpageDisposables.removeValue(forKey: messageId) @@ -1009,9 +1023,9 @@ public final class AccountViewTracker { } } - public func updateUnsupportedMediaForMessageIds(messageIds: Set) { + public func updateUnsupportedMediaForMessageIds(messageIds: Set) { self.queue.async { - var addedMessageIds: [MessageId] = [] + var addedMessageIds: [MessageAndThreadId] = [] let timestamp = Int32(CFAbsoluteTimeGetCurrent()) for messageId in messageIds { let messageTimestamp = self.updatedUnsupportedMediaMessageIdsAndTimestamps[messageId] @@ -1021,14 +1035,14 @@ public final class AccountViewTracker { } } if !addedMessageIds.isEmpty { - for (peerId, messageIds) in messagesIdsGroupedByPeerId(Set(addedMessageIds)) { + for (peerIdAndThreadId, messageIds) in messagesIdsGroupedByPeerId(Set(addedMessageIds)) { let disposableId = self.nextUpdatedUnsupportedMediaDisposableId self.nextUpdatedUnsupportedMediaDisposableId += 1 if let account = self.account { let accountPeerId = account.peerId let signal = account.postbox.transaction { transaction -> Peer? in - if let peer = transaction.getPeer(peerId) { + if let peer = transaction.getPeer(peerIdAndThreadId.peerId) { return peer } else { return nil @@ -1043,9 +1057,15 @@ public final class AccountViewTracker { if let inputPeer = apiInputPeer(peer) { fetchSignal = account.network.request(Api.functions.messages.getScheduledMessages(peer: inputPeer, id: messageIds.map { $0.id })) } - } else if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.CloudGroup { + } else if let messageId = messageIds.first, messageId.namespace == Namespaces.Message.QuickReplyCloud { + if let threadId = peerIdAndThreadId.threadId { + fetchSignal = account.network.request(Api.functions.messages.getQuickReplyMessages(flags: 1 << 0, shortcutId: Int32(clamping: threadId), id: messageIds.map { $0.id }, hash: 0)) + } else { + fetchSignal = .never() + } + } else if peerIdAndThreadId.peerId.namespace == Namespaces.Peer.CloudUser || peerIdAndThreadId.peerId.namespace == Namespaces.Peer.CloudGroup { fetchSignal = account.network.request(Api.functions.messages.getMessages(id: messageIds.map { Api.InputMessage.inputMessageID(id: $0.id) })) - } else if peerId.namespace == Namespaces.Peer.CloudChannel { + } else if peerIdAndThreadId.peerId.namespace == Namespaces.Peer.CloudChannel { if let inputChannel = apiInputChannel(peer) { fetchSignal = account.network.request(Api.functions.channels.getMessages(channel: inputChannel, id: messageIds.map { Api.InputMessage.inputMessageID(id: $0.id) })) } @@ -1815,7 +1835,7 @@ public final class AccountViewTracker { if let strongSelf = self { strongSelf.queue.async { let (messageIds, localWebpages) = pendingWebpages(entries: next.0.entries) - strongSelf.updatePendingWebpages(viewId: viewId, messageIds: messageIds, localWebpages: localWebpages) + strongSelf.updatePendingWebpages(viewId: viewId, threadId: chatLocation.threadId, messageIds: messageIds, localWebpages: localWebpages) let (pollMessageIds, pollMessageDict) = pollMessages(entries: next.0.entries) strongSelf.updatePolls(viewId: viewId, messageIds: pollMessageIds, messages: pollMessageDict) if case let .peer(peerId, _) = chatLocation, peerId.namespace == Namespaces.Peer.CloudChannel { @@ -1828,7 +1848,7 @@ public final class AccountViewTracker { }, disposed: { [weak self] viewId in if let strongSelf = self { strongSelf.queue.async { - strongSelf.updatePendingWebpages(viewId: viewId, messageIds: [], localWebpages: [:]) + strongSelf.updatePendingWebpages(viewId: viewId, threadId: chatLocation.threadId, messageIds: [], localWebpages: [:]) strongSelf.updatePolls(viewId: viewId, messageIds: [], messages: [:]) switch chatLocation { case let .peer(peerId, _): @@ -1945,14 +1965,14 @@ public final class AccountViewTracker { if let strongSelf = self { strongSelf.queue.async { let (messageIds, localWebpages) = pendingWebpages(entries: next.0.entries) - strongSelf.updatePendingWebpages(viewId: viewId, messageIds: messageIds, localWebpages: localWebpages) + strongSelf.updatePendingWebpages(viewId: viewId, threadId: chatLocation.threadId, messageIds: messageIds, localWebpages: localWebpages) strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0, location: chatLocation) } } }, disposed: { [weak self] viewId in if let strongSelf = self { strongSelf.queue.async { - strongSelf.updatePendingWebpages(viewId: viewId, messageIds: [], localWebpages: [:]) + strongSelf.updatePendingWebpages(viewId: viewId, threadId: chatLocation.threadId, messageIds: [], localWebpages: [:]) strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil, location: nil) } } @@ -1962,6 +1982,36 @@ public final class AccountViewTracker { } } + public func quickReplyMessagesViewForLocation(quickReplyId: Int32, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + guard let account = self.account else { + return .never() + } + let chatLocation: ChatLocationInput = .peer(peerId: account.peerId, threadId: Int64(quickReplyId)) + let signal = account.postbox.aroundMessageHistoryViewForLocation(chatLocation, anchor: .upperBound, ignoreMessagesInTimestampRange: nil, count: 200, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .just(Namespaces.Message.allQuickReply), orderStatistics: [], additionalData: additionalData) + return withState(signal, { [weak self] () -> Int32 in + if let strongSelf = self { + return OSAtomicIncrement32(&strongSelf.nextViewId) + } else { + return -1 + } + }, next: { [weak self] next, viewId in + if let strongSelf = self { + strongSelf.queue.async { + let (messageIds, localWebpages) = pendingWebpages(entries: next.0.entries) + strongSelf.updatePendingWebpages(viewId: viewId, threadId: Int64(quickReplyId), messageIds: messageIds, localWebpages: localWebpages) + strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0, location: chatLocation) + } + } + }, disposed: { [weak self] viewId in + if let strongSelf = self { + strongSelf.queue.async { + strongSelf.updatePendingWebpages(viewId: viewId, threadId: Int64(quickReplyId), messageIds: [], localWebpages: [:]) + strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil, location: nil) + } + } + }) + } + public func aroundMessageOfInterestHistoryViewForLocation(_ chatLocation: ChatLocationInput, ignoreMessagesInTimestampRange: ClosedRange? = nil, count: Int, tag: HistoryViewInputTag? = nil, appendMessagesFromTheSameGroup: Bool = false, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = [], useRootInterfaceStateForThread: Bool = false) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> @@ -1994,7 +2044,7 @@ public final class AccountViewTracker { topTaggedMessageIdNamespaces: [], tag: tag, appendMessagesFromTheSameGroup: false, - namespaces: .not(Namespaces.Message.allScheduled), + namespaces: .not(Namespaces.Message.allNonRegular), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData), useRootInterfaceStateForThread: useRootInterfaceStateForThread @@ -2020,7 +2070,7 @@ public final class AccountViewTracker { topTaggedMessageIdNamespaces: [], tag: tag, appendMessagesFromTheSameGroup: false, - namespaces: .not(Namespaces.Message.allScheduled), + namespaces: .not(Namespaces.Message.allNonRegular), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData), useRootInterfaceStateForThread: useRootInterfaceStateForThread @@ -2028,10 +2078,10 @@ public final class AccountViewTracker { } } - return account.postbox.aroundMessageOfInterestHistoryViewForChatLocation(chatLocation, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, customUnreadMessageId: nil, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData), useRootInterfaceStateForThread: useRootInterfaceStateForThread) + return account.postbox.aroundMessageOfInterestHistoryViewForChatLocation(chatLocation, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: .not(Namespaces.Message.allNonRegular), orderStatistics: orderStatistics, customUnreadMessageId: nil, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData), useRootInterfaceStateForThread: useRootInterfaceStateForThread) } } else { - signal = account.postbox.aroundMessageOfInterestHistoryViewForChatLocation(chatLocation, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, customUnreadMessageId: nil, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData), useRootInterfaceStateForThread: useRootInterfaceStateForThread) + signal = account.postbox.aroundMessageOfInterestHistoryViewForChatLocation(chatLocation, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: .not(Namespaces.Message.allNonRegular), orderStatistics: orderStatistics, customUnreadMessageId: nil, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData), useRootInterfaceStateForThread: useRootInterfaceStateForThread) } return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal, fixedCombinedReadStates: nil, addHoleIfNeeded: true) } else { @@ -2041,7 +2091,7 @@ public final class AccountViewTracker { public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocationInput, ignoreMessagesInTimestampRange: ClosedRange? = nil, count: Int, ignoreRelatedChats: Bool, messageId: MessageId, tag: HistoryViewInputTag? = nil, appendMessagesFromTheSameGroup: Bool = false, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = [], useRootInterfaceStateForThread: Bool = false) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { - let signal = account.postbox.aroundIdMessageHistoryViewForLocation(chatLocation, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, count: count, ignoreRelatedChats: ignoreRelatedChats, messageId: messageId, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData), useRootInterfaceStateForThread: useRootInterfaceStateForThread) + let signal = account.postbox.aroundIdMessageHistoryViewForLocation(chatLocation, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, count: count, ignoreRelatedChats: ignoreRelatedChats, messageId: messageId, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: .not(Namespaces.Message.allNonRegular), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData), useRootInterfaceStateForThread: useRootInterfaceStateForThread) return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal, fixedCombinedReadStates: nil, addHoleIfNeeded: false) } else { return .never() @@ -2059,7 +2109,7 @@ public final class AccountViewTracker { case let .message(index): inputAnchor = .index(index) } - let signal = account.postbox.aroundMessageHistoryViewForLocation(chatLocation, anchor: inputAnchor, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, count: count, clipHoles: clipHoles, ignoreRelatedChats: ignoreRelatedChats, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData), useRootInterfaceStateForThread: useRootInterfaceStateForThread) + let signal = account.postbox.aroundMessageHistoryViewForLocation(chatLocation, anchor: inputAnchor, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, count: count, clipHoles: clipHoles, ignoreRelatedChats: ignoreRelatedChats, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: .not(Namespaces.Message.allNonRegular), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData), useRootInterfaceStateForThread: useRootInterfaceStateForThread) return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal, fixedCombinedReadStates: fixedCombinedReadStates, addHoleIfNeeded: false) } else { return .never() diff --git a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift index 79999c1058..0a2a0b1fb4 100644 --- a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift @@ -129,7 +129,9 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes let updatedId: MessageId if let messageId = messageId { var namespace: MessageId.Namespace = Namespaces.Message.Cloud - if let updatedTimestamp = updatedTimestamp { + if Namespaces.Message.allQuickReply.contains(message.id.namespace) { + namespace = Namespaces.Message.QuickReplyCloud + } else if let updatedTimestamp = updatedTimestamp { if message.scheduleTime != nil && message.scheduleTime == updatedTimestamp { namespace = Namespaces.Message.ScheduledCloud } @@ -141,21 +143,17 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes updatedId = currentMessage.id } - for attribute in currentMessage.attributes { - if let attribute = attribute as? OutgoingMessageInfoAttribute { - bubbleUpEmojiOrStickersets = attribute.bubbleUpEmojiOrStickersets - } - } - let media: [Media] var attributes: [MessageAttribute] let text: String let forwardInfo: StoreMessageForwardInfo? + let threadId: Int64? if let apiMessage = apiMessage, let apiMessagePeerId = apiMessage.peerId, let updatedMessage = StoreMessage(apiMessage: apiMessage, accountPeerId: accountPeerId, peerIsForum: transaction.getPeer(apiMessagePeerId)?.isForum ?? false) { media = updatedMessage.media attributes = updatedMessage.attributes text = updatedMessage.text forwardInfo = updatedMessage.forwardInfo + threadId = updatedMessage.threadId } else if case let .updateShortSentMessage(_, _, _, _, _, apiMedia, entities, ttlPeriod) = result { let (mediaValue, _, nonPremium, hasSpoiler, _) = textMediaAndExpirationTimerFromApiMedia(apiMedia, currentMessage.id.peerId) if let mediaValue = mediaValue { @@ -197,16 +195,36 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes } } } + if Namespaces.Message.allQuickReply.contains(message.id.namespace) { + for i in 0 ..< updatedAttributes.count { + if updatedAttributes[i] is OutgoingQuickReplyMessageAttribute { + updatedAttributes.remove(at: i) + break + } + } + } attributes = updatedAttributes text = currentMessage.text forwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) + threadId = currentMessage.threadId } else { media = currentMessage.media attributes = currentMessage.attributes text = currentMessage.text forwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) + threadId = currentMessage.threadId + } + + for attribute in currentMessage.attributes { + if let attribute = attribute as? OutgoingMessageInfoAttribute { + bubbleUpEmojiOrStickersets = attribute.bubbleUpEmojiOrStickersets + } else if let attribute = attribute as? OutgoingQuickReplyMessageAttribute { + if let threadId { + _internal_applySentQuickReplyMessage(transaction: transaction, shortcut: attribute.shortcut, quickReplyId: Int32(clamping: threadId)) + } + } } if let channelPts = channelPts { @@ -255,7 +273,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes let (tags, globalTags) = tagsForStoreMessage(incoming: currentMessage.flags.contains(.Incoming), attributes: attributes, media: media, textEntities: entitiesAttribute?.entities, isPinned: currentMessage.tags.contains(.pinned)) - if currentMessage.id.peerId.namespace == Namespaces.Peer.CloudChannel, !currentMessage.flags.contains(.Incoming), !Namespaces.Message.allScheduled.contains(currentMessage.id.namespace) { + if currentMessage.id.peerId.namespace == Namespaces.Peer.CloudChannel, !currentMessage.flags.contains(.Incoming), !Namespaces.Message.allNonRegular.contains(currentMessage.id.namespace) { let peerId = currentMessage.id.peerId if let peer = transaction.getPeer(peerId) { if let peer = peer as? TelegramChannel { @@ -344,7 +362,9 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage let updatedRawMessageIds = result.updatedRawMessageIds var namespace = Namespaces.Message.Cloud - if let message = messages.first, let apiMessage = result.messages.first, message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { + if Namespaces.Message.allQuickReply.contains(messages[0].id.namespace) { + namespace = Namespaces.Message.QuickReplyCloud + } else if let message = messages.first, let apiMessage = result.messages.first, message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { namespace = Namespaces.Message.ScheduledCloud } @@ -411,6 +431,16 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage var bubbleUpEmojiOrStickersets: [ItemCollectionId] = [] + if let (message, _, updatedMessage) = mapping.first { + for attribute in message.attributes { + if let attribute = attribute as? OutgoingQuickReplyMessageAttribute { + if let threadId = updatedMessage.threadId { + _internal_applySentQuickReplyMessage(transaction: transaction, shortcut: attribute.shortcut, quickReplyId: Int32(clamping: threadId)) + } + } + } + } + for (message, _, updatedMessage) in mapping { transaction.updateMessage(message.id, update: { currentMessage in let updatedId: MessageId diff --git a/submodules/TelegramCore/Sources/State/CloudChatRemoveMessagesOperation.swift b/submodules/TelegramCore/Sources/State/CloudChatRemoveMessagesOperation.swift index 58b9bd40ed..acf9a30261 100644 --- a/submodules/TelegramCore/Sources/State/CloudChatRemoveMessagesOperation.swift +++ b/submodules/TelegramCore/Sources/State/CloudChatRemoveMessagesOperation.swift @@ -1,7 +1,7 @@ import Postbox -func cloudChatAddRemoveMessagesOperation(transaction: Transaction, peerId: PeerId, messageIds: [MessageId], type: CloudChatRemoveMessagesType) { - transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.CloudChatRemoveMessages, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: CloudChatRemoveMessagesOperation(messageIds: messageIds, type: type)) +func cloudChatAddRemoveMessagesOperation(transaction: Transaction, peerId: PeerId, threadId: Int64?, messageIds: [MessageId], type: CloudChatRemoveMessagesType) { + transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.CloudChatRemoveMessages, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: CloudChatRemoveMessagesOperation(messageIds: messageIds, threadId: threadId, type: type)) } func cloudChatAddRemoveChatOperation(transaction: Transaction, peerId: PeerId, reportChatSpam: Bool, deleteGloballyIfPossible: Bool) { @@ -15,7 +15,26 @@ func cloudChatAddClearHistoryOperation(transaction: Transaction, peerId: PeerId, messageIds.append(message.id) return true } - cloudChatAddRemoveMessagesOperation(transaction: transaction, peerId: peerId, messageIds: messageIds, type: .forLocalPeer) + cloudChatAddRemoveMessagesOperation(transaction: transaction, peerId: peerId, threadId: threadId, messageIds: messageIds, type: .forLocalPeer) + } else if type == .quickReplyMessages { + var messageIds: [MessageId] = [] + transaction.withAllMessages(peerId: peerId, namespace: Namespaces.Message.QuickReplyCloud) { message -> Bool in + messageIds.append(message.id) + return true + } + cloudChatAddRemoveMessagesOperation(transaction: transaction, peerId: peerId, threadId: threadId, messageIds: messageIds, type: .forLocalPeer) + + let topMessageId: MessageId? + if let explicitTopMessageId = explicitTopMessageId { + topMessageId = explicitTopMessageId + } else { + topMessageId = transaction.getTopPeerMessageId(peerId: peerId, namespace: Namespaces.Message.QuickReplyCloud) + } + if let topMessageId = topMessageId { + transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.CloudChatRemoveMessages, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: CloudChatClearHistoryOperation(peerId: peerId, topMessageId: topMessageId, threadId: threadId, minTimestamp: minTimestamp, maxTimestamp: maxTimestamp, type: type)) + } else if case .forEveryone = type { + transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.CloudChatRemoveMessages, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: CloudChatClearHistoryOperation(peerId: peerId, topMessageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: .max), threadId: threadId, minTimestamp: minTimestamp, maxTimestamp: maxTimestamp, type: type)) + } } else { let topMessageId: MessageId? if let explicitTopMessageId = explicitTopMessageId { diff --git a/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift b/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift index ddaea2d2c3..4290ad04f4 100644 --- a/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift +++ b/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift @@ -28,78 +28,60 @@ private final class HistoryStateValidationContext { private enum HistoryState { case channel(PeerId, ChannelState) - //case group(PeerGroupId, TelegramPeerGroupState) case scheduledMessages(PeerId) + case quickReplyMessages(PeerId, Int32) var hasInvalidationIndex: Bool { switch self { - case let .channel(_, state): - return state.invalidatedPts != nil - /*case let .group(_, state): - return state.invalidatedStateIndex != nil*/ - case .scheduledMessages: - return false + case let .channel(_, state): + return state.invalidatedPts != nil + case .scheduledMessages: + return false + case .quickReplyMessages: + return false } } func isMessageValid(_ message: Message) -> Bool { switch self { - case let .channel(_, state): - if let invalidatedPts = state.invalidatedPts { - var messagePts: Int32? - inner: for attribute in message.attributes { - if let attribute = attribute as? ChannelMessageStateVersionAttribute { - messagePts = attribute.pts - break inner - } + case let .channel(_, state): + if let invalidatedPts = state.invalidatedPts { + var messagePts: Int32? + inner: for attribute in message.attributes { + if let attribute = attribute as? ChannelMessageStateVersionAttribute { + messagePts = attribute.pts + break inner } - var requiresValidation = false - if let messagePts = messagePts { - if messagePts < invalidatedPts { - requiresValidation = true - } - } else { - requiresValidation = true - } - - return !requiresValidation - } else { - return true } - /*case let .group(_, state): - if let invalidatedStateIndex = state.invalidatedStateIndex { - var messageStateIndex: Int32? - inner: for attribute in message.attributes { - if let attribute = attribute as? PeerGroupMessageStateVersionAttribute { - messageStateIndex = attribute.stateIndex - break inner - } - } - var requiresValidation = false - if let messageStateIndex = messageStateIndex { - if messageStateIndex < invalidatedStateIndex { - requiresValidation = true - } - } else { + + var requiresValidation = false + if let messagePts = messagePts { + if messagePts < invalidatedPts { requiresValidation = true } - return !requiresValidation } else { - return true - }*/ - case .scheduledMessages: - return false + requiresValidation = true + } + + return !requiresValidation + } else { + return true + } + case .scheduledMessages: + return false + case .quickReplyMessages: + return false } } func matchesPeerId(_ peerId: PeerId) -> Bool { switch self { - case let .channel(statePeerId, _): - return statePeerId == peerId - /*case .group: - return true*/ - case let .scheduledMessages(statePeerId): - return statePeerId == peerId + case let .channel(statePeerId, _): + return statePeerId == peerId + case let .scheduledMessages(statePeerId): + return statePeerId == peerId + case let .quickReplyMessages(statePeerId, _): + return statePeerId == peerId } } } @@ -411,6 +393,35 @@ final class HistoryViewStateValidationContexts { })) } } + } else if view.namespaces.contains(Namespaces.Message.QuickReplyCloud) { + if let _ = self.contexts[id] { + } else if let location = location, case let .peer(peerId, threadId) = location { + guard let threadId else { + return + } + + let timestamp = self.network.context.globalTime() + if let previousTimestamp = self.previousPeerValidationTimestamps[peerId], timestamp < previousTimestamp + 60 { + } else { + self.previousPeerValidationTimestamps[peerId] = timestamp + + let context = HistoryStateValidationContext() + self.contexts[id] = context + + let disposable = MetaDisposable() + let batch = HistoryStateValidationBatch(disposable: disposable) + context.batch = batch + + let messages: [Message] = view.entries.map { $0.message }.filter { $0.id.namespace == Namespaces.Message.QuickReplyCloud } + + disposable.set((validateQuickReplyMessagesBatch(postbox: self.postbox, network: self.network, accountPeerId: peerId, tag: nil, messages: messages, historyState: .quickReplyMessages(peerId, Int32(clamping: threadId))) + |> deliverOn(self.queue)).start(completed: { [weak self] in + if let strongSelf = self, let context = strongSelf.contexts[id] { + context.batch = nil + } + })) + } + } } } } @@ -690,6 +701,53 @@ private func validateScheduledMessagesBatch(postbox: Postbox, network: Network, } |> switchToLatest } +private func validateQuickReplyMessagesBatch(postbox: Postbox, network: Network, accountPeerId: PeerId, tag: MessageTags?, messages: [Message], historyState: HistoryState) -> Signal { + return postbox.transaction { transaction -> Signal in + var signal: Signal + switch historyState { + case let .quickReplyMessages(peerId, shortcutId): + if let peer = transaction.getPeer(peerId) { + let hash = hashForScheduledMessages(messages) + signal = network.request(Api.functions.messages.getQuickReplyMessages(flags: 0, shortcutId: shortcutId, id: nil, hash: hash)) + |> map { result -> ValidatedMessages in + let messages: [Api.Message] + let chats: [Api.Chat] + let users: [Api.User] + + switch result { + case let .messages(messages: apiMessages, chats: apiChats, users: apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + case let .messagesSlice(_, _, _, _, messages: apiMessages, chats: apiChats, users: apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + case let .channelMessages(_, _, _, _, apiMessages, apiTopics, apiChats, apiUsers): + messages = apiMessages + let _ = apiTopics + chats = apiChats + users = apiUsers + case .messagesNotModified: + return .notModified + } + return .messages(peer, messages, chats, users, nil) + } + } else { + signal = .complete() + } + default: + signal = .complete() + } + var previous: [MessageId: Message] = [:] + for message in messages { + previous[message.id] = message + } + return validateBatch(postbox: postbox, network: network, transaction: transaction, accountPeerId: accountPeerId, tag: tag, historyState: historyState, signal: signal, previous: previous, messageNamespace: Namespaces.Message.QuickReplyCloud) + } + |> switchToLatest +} + private func validateBatch(postbox: Postbox, network: Network, transaction: Transaction, accountPeerId: PeerId, tag: MessageTags?, historyState: HistoryState, signal: Signal, previous: [MessageId: Message], messageNamespace: MessageId.Namespace) -> Signal { return signal |> map(Optional.init) diff --git a/submodules/TelegramCore/Sources/State/ManagedCloudChatRemoveMessagesOperations.swift b/submodules/TelegramCore/Sources/State/ManagedCloudChatRemoveMessagesOperations.swift index 1e4572414b..18743c8450 100644 --- a/submodules/TelegramCore/Sources/State/ManagedCloudChatRemoveMessagesOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedCloudChatRemoveMessagesOperations.swift @@ -127,10 +127,14 @@ func managedCloudChatRemoveMessagesOperations(postbox: Postbox, network: Network private func removeMessages(postbox: Postbox, network: Network, stateManager: AccountStateManager, peer: Peer, operation: CloudChatRemoveMessagesOperation) -> Signal { var isScheduled = false + var isQuickReply = false for id in operation.messageIds { if id.namespace == Namespaces.Message.ScheduledCloud { isScheduled = true break + } else if id.namespace == Namespaces.Message.QuickReplyCloud { + isQuickReply = true + break } } @@ -160,6 +164,32 @@ private func removeMessages(postbox: Postbox, network: Network, stateManager: Ac } else { return .complete() } + } else if isQuickReply { + if let threadId = operation.threadId { + var signal: Signal = .complete() + for s in stride(from: 0, to: operation.messageIds.count, by: 100) { + let ids = Array(operation.messageIds[s ..< min(s + 100, operation.messageIds.count)]) + let partSignal = network.request(Api.functions.messages.deleteQuickReplyMessages(shortcutId: Int32(clamping: threadId), id: ids.map(\.id))) + |> map { result -> Api.Updates? in + return result + } + |> `catch` { _ in + return .single(nil) + } + |> mapToSignal { updates -> Signal in + if let updates = updates { + stateManager.addUpdates(updates) + } + return .complete() + } + + signal = signal + |> then(partSignal) + } + return signal + } else { + return .complete() + } } else if peer.id.namespace == Namespaces.Peer.CloudChannel { if let inputChannel = apiInputChannel(peer) { var signal: Signal = .complete() @@ -329,7 +359,7 @@ private func removeChat(transaction: Transaction, postbox: Postbox, network: Net return requestClearHistory(postbox: postbox, network: network, stateManager: stateManager, inputPeer: inputPeer, maxId: operation.topMessageId?.id ?? Int32.max - 1, justClear: false, minTimestamp: nil, maxTimestamp: nil, type: operation.deleteGloballyIfPossible ? .forEveryone : .forLocalPeer) |> then(reportSignal) |> then(postbox.transaction { transaction -> Void in - _internal_clearHistory(transaction: transaction, mediaBox: postbox.mediaBox, peerId: peer.id, threadId: nil, namespaces: .not(Namespaces.Message.allScheduled)) + _internal_clearHistory(transaction: transaction, mediaBox: postbox.mediaBox, peerId: peer.id, threadId: nil, namespaces: .not(Namespaces.Message.allNonRegular)) }) } else { return .complete() @@ -386,6 +416,27 @@ private func requestClearHistory(postbox: Postbox, network: Network, stateManage private func _internal_clearHistory(transaction: Transaction, postbox: Postbox, network: Network, stateManager: AccountStateManager, peer: Peer, operation: CloudChatClearHistoryOperation) -> Signal { if peer.id.namespace == Namespaces.Peer.CloudGroup || peer.id.namespace == Namespaces.Peer.CloudUser { + if case .quickReplyMessages = operation.type { + guard let threadId = operation.threadId else { + return .complete() + } + + let signal = network.request(Api.functions.messages.deleteQuickReplyShortcut(shortcutId: Int32(clamping: threadId))) + |> map { result -> Api.Bool? in + return result + } + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + return .fail(true) + } + return (signal |> restart) + |> `catch` { _ -> Signal in + return .complete() + } + } + if let inputPeer = apiInputPeer(peer) { if peer.id == stateManager.accountPeerId, let threadId = operation.threadId { guard let inputSubPeer = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer) else { @@ -407,7 +458,7 @@ private func _internal_clearHistory(transaction: Transaction, postbox: Postbox, return result } |> `catch` { _ -> Signal in - return .fail(true) + return .single(nil) } |> mapToSignal { result -> Signal in if let result = result { diff --git a/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift b/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift index f253215b50..ace5e77447 100644 --- a/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift +++ b/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift @@ -21,7 +21,7 @@ private func md5Hash(_ data: Data) -> Md5Hash { return Md5Hash(data: hashData) } -private func md5StringHash(_ string: String) -> UInt64 { +func md5StringHash(_ string: String) -> UInt64 { guard let data = string.data(using: .utf8) else { return 0 } diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index 85d6551e74..bed6a901b1 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -793,6 +793,7 @@ public final class PendingMessageManager { var replyToStoryId: StoryId? var scheduleTime: Int32? var sendAsPeerId: PeerId? + var quickReply: OutgoingQuickReplyMessageAttribute? var flags: Int32 = 0 @@ -821,6 +822,8 @@ public final class PendingMessageManager { hideCaptions = attribute.hideCaptions } else if let attribute = attribute as? SendAsMessageAttribute { sendAsPeerId = attribute.peerId + } else if let attribute = attribute as? OutgoingQuickReplyMessageAttribute { + quickReply = attribute } } @@ -871,6 +874,16 @@ public final class PendingMessageManager { topMsgId = Int32(clamping: threadId) } + var quickReplyShortcut: Api.InputQuickReplyShortcut? + if let quickReply { + if let threadId = messages[0].0.threadId, !"".isEmpty { + quickReplyShortcut = .inputQuickReplyShortcutId(shortcutId: Int32(clamping: threadId)) + } else { + quickReplyShortcut = .inputQuickReplyShortcut(shortcut: quickReply.shortcut) + } + flags |= 1 << 17 + } + let forwardPeerIds = Set(forwardIds.map { $0.0.peerId }) if forwardPeerIds.count != 1 { assertionFailure() @@ -878,7 +891,7 @@ public final class PendingMessageManager { } else if let inputSourcePeerId = forwardPeerIds.first, let inputSourcePeer = transaction.getPeer(inputSourcePeerId).flatMap(apiInputPeer) { let dependencyTag = PendingMessageRequestDependencyTag(messageId: messages[0].0.id) - sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: inputSourcePeer, id: forwardIds.map { $0.0.id }, randomId: forwardIds.map { $0.1 }, toPeer: inputPeer, topMsgId: topMsgId, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: inputSourcePeer, id: forwardIds.map { $0.0.id }, randomId: forwardIds.map { $0.1 }, toPeer: inputPeer, topMsgId: topMsgId, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut), tag: dependencyTag) } else { assertionFailure() sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "Invalid forward source")) @@ -993,7 +1006,17 @@ public final class PendingMessageManager { } } - sendMessageRequest = network.request(Api.functions.messages.sendMultiMedia(flags: flags, peer: inputPeer, replyTo: replyTo, multiMedia: singleMedias, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil)) + var quickReplyShortcut: Api.InputQuickReplyShortcut? + if let quickReply { + if let threadId = messages[0].0.threadId, !"".isEmpty { + quickReplyShortcut = .inputQuickReplyShortcutId(shortcutId: Int32(clamping: threadId)) + } else { + quickReplyShortcut = .inputQuickReplyShortcut(shortcut: quickReply.shortcut) + } + flags |= 1 << 17 + } + + sendMessageRequest = network.request(Api.functions.messages.sendMultiMedia(flags: flags, peer: inputPeer, replyTo: replyTo, multiMedia: singleMedias, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut)) } return sendMessageRequest @@ -1168,6 +1191,7 @@ public final class PendingMessageManager { var scheduleTime: Int32? var sendAsPeerId: PeerId? var bubbleUpEmojiOrStickersets = false + var quickReply: OutgoingQuickReplyMessageAttribute? var flags: Int32 = 0 @@ -1202,6 +1226,8 @@ public final class PendingMessageManager { scheduleTime = attribute.scheduleTime } else if let attribute = attribute as? SendAsMessageAttribute { sendAsPeerId = attribute.peerId + } else if let attribute = attribute as? OutgoingQuickReplyMessageAttribute { + quickReply = attribute } } @@ -1291,7 +1317,17 @@ public final class PendingMessageManager { } } - sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil), info: .acknowledgement, tag: dependencyTag) + var quickReplyShortcut: Api.InputQuickReplyShortcut? + if let quickReply { + if let threadId = message.threadId, !"".isEmpty { + quickReplyShortcut = .inputQuickReplyShortcutId(shortcutId: Int32(clamping: threadId)) + } else { + quickReplyShortcut = .inputQuickReplyShortcut(shortcut: quickReply.shortcut) + } + flags |= 1 << 17 + } + + sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut), info: .acknowledgement, tag: dependencyTag) case let .media(inputMedia, text): if bubbleUpEmojiOrStickersets { flags |= Int32(1 << 15) @@ -1355,8 +1391,18 @@ public final class PendingMessageManager { flags |= 1 << 16 } } + + var quickReplyShortcut: Api.InputQuickReplyShortcut? + if let quickReply { + if let threadId = message.threadId, !"".isEmpty { + quickReplyShortcut = .inputQuickReplyShortcutId(shortcutId: Int32(clamping: threadId)) + } else { + quickReplyShortcut = .inputQuickReplyShortcut(shortcut: quickReply.shortcut) + } + flags |= 1 << 17 + } - sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut), tag: dependencyTag) |> map(NetworkRequestResult.result) case let .forward(sourceInfo): var topMsgId: Int32? @@ -1365,8 +1411,18 @@ public final class PendingMessageManager { topMsgId = Int32(clamping: threadId) } + var quickReplyShortcut: Api.InputQuickReplyShortcut? + if let quickReply { + if let threadId = message.threadId, !"".isEmpty { + quickReplyShortcut = .inputQuickReplyShortcutId(shortcutId: Int32(clamping: threadId)) + } else { + quickReplyShortcut = .inputQuickReplyShortcut(shortcut: quickReply.shortcut) + } + flags |= 1 << 17 + } + if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) { - sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut), tag: dependencyTag) |> map(NetworkRequestResult.result) } else { sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal")) @@ -1429,7 +1485,17 @@ public final class PendingMessageManager { } } - sendMessageRequest = network.request(Api.functions.messages.sendInlineBotResult(flags: flags, peer: inputPeer, replyTo: replyTo, randomId: uniqueId, queryId: chatContextResult.queryId, id: chatContextResult.id, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil)) + var quickReplyShortcut: Api.InputQuickReplyShortcut? + if let quickReply { + if let threadId = message.threadId, !"".isEmpty { + quickReplyShortcut = .inputQuickReplyShortcutId(shortcutId: Int32(clamping: threadId)) + } else { + quickReplyShortcut = .inputQuickReplyShortcut(shortcut: quickReply.shortcut) + } + flags |= 1 << 17 + } + + sendMessageRequest = network.request(Api.functions.messages.sendInlineBotResult(flags: flags, peer: inputPeer, replyTo: replyTo, randomId: uniqueId, queryId: chatContextResult.queryId, id: chatContextResult.id, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut)) |> map(NetworkRequestResult.result) case .messageScreenshot: let replyTo: Api.InputReplyTo @@ -1557,7 +1623,16 @@ public final class PendingMessageManager { private func applySentMessage(postbox: Postbox, stateManager: AccountStateManager, message: Message, content: PendingMessageUploadedContentAndReuploadInfo, result: Api.Updates) -> Signal { var apiMessage: Api.Message? for resultMessage in result.messages { - if let id = resultMessage.id(namespace: Namespaces.Message.allScheduled.contains(message.id.namespace) ? Namespaces.Message.ScheduledCloud : Namespaces.Message.Cloud) { + let targetNamespace: MessageId.Namespace + if Namespaces.Message.allScheduled.contains(message.id.namespace) { + targetNamespace = Namespaces.Message.ScheduledCloud + } else if Namespaces.Message.allQuickReply.contains(message.id.namespace) { + targetNamespace = Namespaces.Message.QuickReplyCloud + } else { + targetNamespace = Namespaces.Message.Cloud + } + + if let id = resultMessage.id(namespace: targetNamespace) { if id.peerId == message.id.peerId { apiMessage = resultMessage break @@ -1567,7 +1642,9 @@ public final class PendingMessageManager { let silent = message.muted var namespace = Namespaces.Message.Cloud - if let apiMessage = apiMessage, let id = apiMessage.id(namespace: message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp ? Namespaces.Message.ScheduledCloud : Namespaces.Message.Cloud) { + if message.id.namespace == Namespaces.Message.QuickReplyLocal { + namespace = Namespaces.Message.QuickReplyCloud + } else if let apiMessage = apiMessage, let id = apiMessage.id(namespace: message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp ? Namespaces.Message.ScheduledCloud : Namespaces.Message.Cloud) { namespace = id.namespace if let attribute = message.attributes.first(where: { $0 is OutgoingMessageInfoAttribute }) as? OutgoingMessageInfoAttribute, let correlationId = attribute.correlationId { @@ -1594,7 +1671,9 @@ public final class PendingMessageManager { private func applySentGroupMessages(postbox: Postbox, stateManager: AccountStateManager, messages: [Message], result: Api.Updates) -> Signal { var silent = false var namespace = Namespaces.Message.Cloud - if let message = messages.first, let apiMessage = result.messages.first, message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { + if let message = messages.first, message.id.namespace == Namespaces.Message.QuickReplyLocal { + namespace = Namespaces.Message.QuickReplyCloud + } else if let message = messages.first, let apiMessage = result.messages.first, message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { namespace = Namespaces.Message.ScheduledCloud if message.muted { silent = true @@ -1605,7 +1684,7 @@ public final class PendingMessageManager { for i in 0 ..< messages.count { let message = messages[i] let apiMessage = result.messages[i] - if let id = apiMessage.id(namespace: message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp ? Namespaces.Message.ScheduledCloud : Namespaces.Message.Cloud) { + if let id = apiMessage.id(namespace: namespace) { if let attribute = message.attributes.first(where: { $0 is OutgoingMessageInfoAttribute }) as? OutgoingMessageInfoAttribute, let correlationId = attribute.correlationId { self.correlationIdToSentMessageId.with { value in value.mapping[correlationId] = id diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index a75e264534..a7fcc88093 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -271,6 +271,8 @@ extension Api.Update { return message case let .updateNewScheduledMessage(message): return message + case let .updateQuickReplyMessage(message): + return message default: return nil } @@ -334,6 +336,8 @@ extension Api.Update { return [peer.peerId] case let .updateNewScheduledMessage(message): return apiMessagePeerIds(message) + case let .updateQuickReplyMessage(message): + return apiMessagePeerIds(message) default: return [] } @@ -349,6 +353,8 @@ extension Api.Update { return apiMessageAssociatedMessageIds(message) case let .updateNewScheduledMessage(message): return apiMessageAssociatedMessageIds(message) + case let .updateQuickReplyMessage(message): + return apiMessageAssociatedMessageIds(message) default: break } diff --git a/submodules/TelegramCore/Sources/SyncCore/QuickReplyMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/QuickReplyMessageAttribute.swift new file mode 100644 index 0000000000..9df4ade399 --- /dev/null +++ b/submodules/TelegramCore/Sources/SyncCore/QuickReplyMessageAttribute.swift @@ -0,0 +1,23 @@ +import Foundation +import Postbox +import TelegramApi + +public final class OutgoingQuickReplyMessageAttribute: Equatable, MessageAttribute { + public let shortcut: String + + public init(shortcut: String) { + self.shortcut = shortcut + } + + required public init(decoder: PostboxDecoder) { + self.shortcut = decoder.decodeStringForKey("s", orElse: "") + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeString(self.shortcut, forKey: "s") + } + + public static func ==(lhs: OutgoingQuickReplyMessageAttribute, rhs: OutgoingQuickReplyMessageAttribute) -> Bool { + return true + } +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift index 402f4fa81d..49837a1318 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift @@ -429,8 +429,8 @@ extension TelegramBusinessLocation.Coordinates { extension TelegramBusinessLocation { convenience init(apiLocation: Api.BusinessLocation) { switch apiLocation { - case let .businessLocation(geoPoint, address): - self.init(address: address, coordinates: Coordinates(apiGeoPoint: geoPoint)) + case let .businessLocation(_, geoPoint, address): + self.init(address: address, coordinates: geoPoint.flatMap { Coordinates(apiGeoPoint: $0) }) } } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CloudChatRemoveMessagesOperation.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CloudChatRemoveMessagesOperation.swift index 40d830bf1b..41e23ccdcf 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CloudChatRemoveMessagesOperation.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CloudChatRemoveMessagesOperation.swift @@ -24,21 +24,29 @@ public extension CloudChatRemoveMessagesType { public final class CloudChatRemoveMessagesOperation: PostboxCoding { public let messageIds: [MessageId] + public let threadId: Int64? public let type: CloudChatRemoveMessagesType - public init(messageIds: [MessageId], type: CloudChatRemoveMessagesType) { + public init(messageIds: [MessageId], threadId: Int64?, type: CloudChatRemoveMessagesType) { self.messageIds = messageIds + self.threadId = threadId self.type = type } public init(decoder: PostboxDecoder) { self.messageIds = MessageId.decodeArrayFromBuffer(decoder.decodeBytesForKeyNoCopy("i")!) + self.threadId = decoder.decodeOptionalInt64ForKey("threadId") self.type = CloudChatRemoveMessagesType(rawValue: decoder.decodeInt32ForKey("t", orElse: 0))! } public func encode(_ encoder: PostboxEncoder) { let buffer = WriteBuffer() MessageId.encodeArrayToBuffer(self.messageIds, buffer: buffer) + if let threadId = self.threadId { + encoder.encodeInt64(threadId, forKey: "threadId") + } else { + encoder.encodeNil(forKey: "threadId") + } encoder.encodeBytes(buffer, forKey: "i") encoder.encodeInt32(self.type.rawValue, forKey: "t") } @@ -88,6 +96,7 @@ public enum CloudChatClearHistoryType: Int32 { case forLocalPeer case forEveryone case scheduledMessages + case quickReplyMessages } public enum InteractiveHistoryClearingType: Int32 { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_MediaReference.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_MediaReference.swift index 66f409e0fd..57a8473844 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_MediaReference.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_MediaReference.swift @@ -7,7 +7,7 @@ public struct MessageReference: PostboxCoding, Hashable, Equatable { switch content { case .none: return nil - case let .message(peer, _, _, _, _, _): + case let .message(peer, _, _, _, _, _, _): return peer } } @@ -15,7 +15,7 @@ public struct MessageReference: PostboxCoding, Hashable, Equatable { switch content { case .none: return nil - case let .message(_, author, _, _, _, _): + case let .message(_, author, _, _, _, _, _): return author } } @@ -24,7 +24,7 @@ public struct MessageReference: PostboxCoding, Hashable, Equatable { switch content { case .none: return nil - case let .message(_, _, id, _, _, _): + case let .message(_, _, id, _, _, _, _): return id } } @@ -33,7 +33,7 @@ public struct MessageReference: PostboxCoding, Hashable, Equatable { switch content { case .none: return nil - case let .message(_, _, _, timestamp, _, _): + case let .message(_, _, _, timestamp, _, _, _): return timestamp } } @@ -42,7 +42,7 @@ public struct MessageReference: PostboxCoding, Hashable, Equatable { switch content { case .none: return nil - case let .message(_, _, _, _, incoming, _): + case let .message(_, _, _, _, incoming, _, _): return incoming } } @@ -51,11 +51,20 @@ public struct MessageReference: PostboxCoding, Hashable, Equatable { switch content { case .none: return nil - case let .message(_, _, _, _, _, secret): + case let .message(_, _, _, _, _, secret, _): return secret } } + public var threadId: Int64? { + switch content { + case .none: + return nil + case let .message(_, _, _, _, _, _, threadId): + return threadId + } + } + public init(_ message: Message) { if message.id.namespace != Namespaces.Message.Local, let peer = message.peers[message.id.peerId], let inputPeer = PeerReference(peer) { let author: PeerReference? @@ -64,13 +73,13 @@ public struct MessageReference: PostboxCoding, Hashable, Equatable { } else { author = nil } - self.content = .message(peer: inputPeer, author: author, id: message.id, timestamp: message.timestamp, incoming: message.flags.contains(.Incoming), secret: message.containsSecretMedia) + self.content = .message(peer: inputPeer, author: author, id: message.id, timestamp: message.timestamp, incoming: message.flags.contains(.Incoming), secret: message.containsSecretMedia, threadId: message.threadId) } else { self.content = .none } } - public init(peer: Peer, author: Peer?, id: MessageId, timestamp: Int32, incoming: Bool, secret: Bool) { + public init(peer: Peer, author: Peer?, id: MessageId, timestamp: Int32, incoming: Bool, secret: Bool, threadId: Int64?) { if let inputPeer = PeerReference(peer) { let a: PeerReference? if let peer = author { @@ -78,7 +87,7 @@ public struct MessageReference: PostboxCoding, Hashable, Equatable { } else { a = nil } - self.content = .message(peer: inputPeer, author: a, id: id, timestamp: timestamp, incoming: incoming, secret: secret) + self.content = .message(peer: inputPeer, author: a, id: id, timestamp: timestamp, incoming: incoming, secret: secret, threadId: threadId) } else { self.content = .none } @@ -95,14 +104,14 @@ public struct MessageReference: PostboxCoding, Hashable, Equatable { public enum MessageReferenceContent: PostboxCoding, Hashable, Equatable { case none - case message(peer: PeerReference, author: PeerReference?, id: MessageId, timestamp: Int32, incoming: Bool, secret: Bool) + case message(peer: PeerReference, author: PeerReference?, id: MessageId, timestamp: Int32, incoming: Bool, secret: Bool, threadId: Int64?) public init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("_r", orElse: 0) { case 0: self = .none case 1: - self = .message(peer: decoder.decodeObjectForKey("p", decoder: { PeerReference(decoder: $0) }) as! PeerReference, author: decoder.decodeObjectForKey("author") as? PeerReference, id: MessageId(peerId: PeerId(decoder.decodeInt64ForKey("i.p", orElse: 0)), namespace: decoder.decodeInt32ForKey("i.n", orElse: 0), id: decoder.decodeInt32ForKey("i.i", orElse: 0)), timestamp: 0, incoming: false, secret: false) + self = .message(peer: decoder.decodeObjectForKey("p", decoder: { PeerReference(decoder: $0) }) as! PeerReference, author: decoder.decodeObjectForKey("author") as? PeerReference, id: MessageId(peerId: PeerId(decoder.decodeInt64ForKey("i.p", orElse: 0)), namespace: decoder.decodeInt32ForKey("i.n", orElse: 0), id: decoder.decodeInt32ForKey("i.i", orElse: 0)), timestamp: 0, incoming: false, secret: false, threadId: decoder.decodeOptionalInt64ForKey("tid")) default: assertionFailure() self = .none @@ -113,7 +122,7 @@ public enum MessageReferenceContent: PostboxCoding, Hashable, Equatable { switch self { case .none: encoder.encodeInt32(0, forKey: "_r") - case let .message(peer, author, id, _, _, _): + case let .message(peer, author, id, _, _, _, threadId): encoder.encodeInt32(1, forKey: "_r") encoder.encodeObject(peer, forKey: "p") if let author = author { @@ -124,6 +133,11 @@ public enum MessageReferenceContent: PostboxCoding, Hashable, Equatable { encoder.encodeInt64(id.peerId.toInt64(), forKey: "i.p") encoder.encodeInt32(id.namespace, forKey: "i.n") encoder.encodeInt32(id.id, forKey: "i.i") + if let threadId { + encoder.encodeInt64(threadId, forKey: "tid") + } else { + encoder.encodeNil(forKey: "tid") + } } } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index a483fd4329..70d9a83496 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -8,8 +8,12 @@ public struct Namespaces { public static let SecretIncoming: Int32 = 2 public static let ScheduledCloud: Int32 = 3 public static let ScheduledLocal: Int32 = 4 + public static let QuickReplyCloud: Int32 = 5 + public static let QuickReplyLocal: Int32 = 6 public static let allScheduled: Set = Set([Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal]) + public static let allQuickReply: Set = Set([Namespaces.Message.QuickReplyCloud, Namespaces.Message.QuickReplyLocal]) + public static let allNonRegular: Set = Set([Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal, Namespaces.Message.QuickReplyCloud, Namespaces.Message.QuickReplyLocal]) } public struct Media { @@ -280,7 +284,7 @@ private enum PreferencesKeyValues: Int32 { case audioTranscriptionTrialState = 33 case didCacheSavedMessageTagsPrefix = 34 case displaySavedChatsAsTopics = 35 - case shortcutMessages = 36 + case shortcutMessages = 37 } public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift index db2a419409..c2986ca224 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift @@ -138,8 +138,12 @@ public extension TelegramEngine { var inputAddress: String? if let businessLocation { flags |= 1 << 0 - inputGeoPoint = businessLocation.coordinates?.apiInputGeoPoint ?? .inputGeoPointEmpty inputAddress = businessLocation.address + + inputGeoPoint = businessLocation.coordinates?.apiInputGeoPoint + if inputGeoPoint != nil { + flags |= 1 << 1 + } } let remoteApply: Signal = self.account.network.request(Api.functions.account.updateBusinessLocation(flags: flags, geoPoint: inputGeoPoint, address: inputAddress)) @@ -158,12 +162,28 @@ public extension TelegramEngine { |> then(remoteApply) } - public func shortcutMessages() -> Signal { - return _internal_shortcutMessages(account: self.account) + public func shortcutMessageList() -> Signal { + return _internal_shortcutMessageList(account: self.account) } - public func updateShortcutMessages(state: QuickReplyMessageShortcutsState) { - let _ = _internal_updateShortcutMessages(account: self.account, state: state).startStandalone() + public func keepShortcutMessageListUpdated() -> Signal { + return _internal_keepShortcutMessagesUpdated(account: self.account) + } + + public func editMessageShortcut(id: Int32, shortcut: String) { + let _ = _internal_editMessageShortcut(account: self.account, id: id, shortcut: shortcut).startStandalone() + } + + public func deleteMessageShortcuts(ids: [Int32]) { + let _ = _internal_deleteMessageShortcuts(account: self.account, ids: ids).startStandalone() + } + + public func reorderMessageShortcuts(ids: [Int32], completion: @escaping () -> Void) { + let _ = _internal_reorderMessageShortcuts(account: self.account, ids: ids, localCompletion: completion).startStandalone() + } + + public func sendMessageShortcut(peerId: EnginePeer.Id, id: Int32) { + let _ = _internal_sendMessageShortcut(account: self.account, peerId: peerId, id: id).startStandalone() } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/DeleteMessagesInteractively.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/DeleteMessagesInteractively.swift index 12e0fa696a..6fb62b8142 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/DeleteMessagesInteractively.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/DeleteMessagesInteractively.swift @@ -12,35 +12,51 @@ func _internal_deleteMessagesInteractively(account: Account, messageIds: [Messag } func deleteMessagesInteractively(transaction: Transaction, stateManager: AccountStateManager?, postbox: Postbox, messageIds initialMessageIds: [MessageId], type: InteractiveMessagesDeletionType, deleteAllInGroup: Bool = false, removeIfPossiblyDelivered: Bool) { - var messageIds: [MessageId] = [] + var messageIds: [MessageAndThreadId] = [] if deleteAllInGroup { + var tempIds: [MessageId] = initialMessageIds for id in initialMessageIds { if let group = transaction.getMessageGroup(id) ?? transaction.getMessageForwardedGroup(id) { for message in group { - if !messageIds.contains(message.id) { - messageIds.append(message.id) + if !tempIds.contains(message.id) { + tempIds.append(message.id) } } } else { - messageIds.append(id) + tempIds.append(id) + } + } + + messageIds = tempIds.map { id in + if id.namespace == Namespaces.Message.QuickReplyCloud { + if let message = transaction.getMessage(id) { + return MessageAndThreadId(messageId: id, threadId: message.threadId) + } else { + return MessageAndThreadId(messageId: id, threadId: nil) + } + } else { + return MessageAndThreadId(messageId: id, threadId: nil) } } } else { - messageIds = initialMessageIds - } - - var messageIdsByPeerId: [PeerId: [MessageId]] = [:] - for id in messageIds { - if messageIdsByPeerId[id.peerId] == nil { - messageIdsByPeerId[id.peerId] = [id] - } else { - messageIdsByPeerId[id.peerId]!.append(id) + messageIds = initialMessageIds.map { id in + if id.namespace == Namespaces.Message.QuickReplyCloud { + if let message = transaction.getMessage(id) { + return MessageAndThreadId(messageId: id, threadId: message.threadId) + } else { + return MessageAndThreadId(messageId: id, threadId: nil) + } + } else { + return MessageAndThreadId(messageId: id, threadId: nil) + } } } var uniqueIds: [Int64: PeerId] = [:] - for (peerId, peerMessageIds) in messageIdsByPeerId { + for (peerAndThreadId, peerMessageIds) in messagesIdsGroupedByPeerId(messageIds) { + let peerId = peerAndThreadId.peerId + let threadId = peerAndThreadId.threadId for id in peerMessageIds { if let message = transaction.getMessage(id) { for attribute in message.attributes { @@ -53,13 +69,13 @@ func deleteMessagesInteractively(transaction: Transaction, stateManager: Account if peerId.namespace == Namespaces.Peer.CloudChannel || peerId.namespace == Namespaces.Peer.CloudGroup || peerId.namespace == Namespaces.Peer.CloudUser { let remoteMessageIds = peerMessageIds.filter { id in - if id.namespace == Namespaces.Message.Local || id.namespace == Namespaces.Message.ScheduledLocal { + if id.namespace == Namespaces.Message.Local || id.namespace == Namespaces.Message.ScheduledLocal || id.namespace == Namespaces.Message.QuickReplyLocal { return false } return true } if !remoteMessageIds.isEmpty { - cloudChatAddRemoveMessagesOperation(transaction: transaction, peerId: peerId, messageIds: remoteMessageIds, type: CloudChatRemoveMessagesType(type)) + cloudChatAddRemoveMessagesOperation(transaction: transaction, peerId: peerId, threadId: threadId, messageIds: remoteMessageIds, type: CloudChatRemoveMessagesType(type)) } } else if peerId.namespace == Namespaces.Peer.SecretChat { if let state = transaction.getPeerChatState(peerId) as? SecretChatState { @@ -87,9 +103,9 @@ func deleteMessagesInteractively(transaction: Transaction, stateManager: Account } } } - _internal_deleteMessages(transaction: transaction, mediaBox: postbox.mediaBox, ids: messageIds) + _internal_deleteMessages(transaction: transaction, mediaBox: postbox.mediaBox, ids: messageIds.map(\.messageId)) - stateManager?.notifyDeletedMessages(messageIds: messageIds) + stateManager?.notifyDeletedMessages(messageIds: messageIds.map(\.messageId)) if !uniqueIds.isEmpty && removeIfPossiblyDelivered { stateManager?.removePossiblyDeliveredMessages(uniqueIds: uniqueIds) @@ -102,7 +118,7 @@ func _internal_clearHistoryInRangeInteractively(postbox: Postbox, peerId: PeerId cloudChatAddClearHistoryOperation(transaction: transaction, peerId: peerId, threadId: threadId, explicitTopMessageId: nil, minTimestamp: minTimestamp, maxTimestamp: maxTimestamp, type: CloudChatClearHistoryType(type)) if type == .scheduledMessages { } else { - _internal_clearHistoryInRange(transaction: transaction, mediaBox: postbox.mediaBox, peerId: peerId, threadId: threadId, minTimestamp: minTimestamp, maxTimestamp: maxTimestamp, namespaces: .not(Namespaces.Message.allScheduled)) + _internal_clearHistoryInRange(transaction: transaction, mediaBox: postbox.mediaBox, peerId: peerId, threadId: threadId, minTimestamp: minTimestamp, maxTimestamp: maxTimestamp, namespaces: .not(Namespaces.Message.allNonRegular)) } } else if peerId.namespace == Namespaces.Peer.SecretChat { /*_internal_clearHistory(transaction: transaction, mediaBox: postbox.mediaBox, peerId: peerId, namespaces: .all) @@ -141,7 +157,7 @@ func _internal_clearHistoryInteractively(postbox: Postbox, peerId: PeerId, threa topIndex = topMessage.index } - _internal_clearHistory(transaction: transaction, mediaBox: postbox.mediaBox, peerId: peerId, threadId: threadId, namespaces: .not(Namespaces.Message.allScheduled)) + _internal_clearHistory(transaction: transaction, mediaBox: postbox.mediaBox, peerId: peerId, threadId: threadId, namespaces: .not(Namespaces.Message.allNonRegular)) if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData, let migrationReference = cachedData.migrationReference { cloudChatAddClearHistoryOperation(transaction: transaction, peerId: migrationReference.maxMessageId.peerId, threadId: threadId, explicitTopMessageId: MessageId(peerId: migrationReference.maxMessageId.peerId, namespace: migrationReference.maxMessageId.namespace, id: migrationReference.maxMessageId.id + 1), minTimestamp: nil, maxTimestamp: nil, type: CloudChatClearHistoryType(type)) _internal_clearHistory(transaction: transaction, mediaBox: postbox.mediaBox, peerId: migrationReference.maxMessageId.peerId, threadId: threadId, namespaces: .all) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift index 34dde0d272..e8d6cbab5e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift @@ -5,89 +5,12 @@ import TelegramApi import MtProtoKit public final class QuickReplyMessageShortcut: Codable, Equatable { - private final class CodableMessage: Codable { - let message: Message - - init(message: Message) { - self.message = message - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: StringCodingKey.self) - - var media: [Media] = [] - if let mediaData = try container.decodeIfPresent(Data.self, forKey: "media") { - if let value = PostboxDecoder(buffer: MemoryBuffer(data: mediaData)).decodeRootObject() as? Media { - media.append(value) - } - } - - var attributes: [MessageAttribute] = [] - if let attributesData = try container.decodeIfPresent([Data].self, forKey: "attributes") { - for attribute in attributesData { - if let value = PostboxDecoder(buffer: MemoryBuffer(data: attribute)).decodeRootObject() as? MessageAttribute { - attributes.append(value) - } - } - } - - self.message = Message( - stableId: 0, - stableVersion: 0, - id: MessageId(peerId: PeerId(namespace: PeerId.Namespace._internalFromInt32Value(0), id: PeerId.Id._internalFromInt64Value(0)), namespace: 0, id: 0), - globallyUniqueId: nil, - groupingKey: nil, - groupInfo: nil, - threadId: nil, - timestamp: 0, - flags: [], - tags: [], - globalTags: [], - localTags: [], - customTags: [], - forwardInfo: nil, - author: nil, - text: try container.decode(String.self, forKey: "text"), - attributes: attributes, - media: media, - peers: SimpleDictionary(), - associatedMessages: SimpleDictionary(), - associatedMessageIds: [], - associatedMedia: [:], - associatedThreadInfo: nil, - associatedStories: [:] - ) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: StringCodingKey.self) - - if let media = self.message.media.first { - let mediaEncoder = PostboxEncoder() - mediaEncoder.encodeRootObject(media) - try container.encode(mediaEncoder.makeData(), forKey: "media") - } - - var attributesData: [Data] = [] - for attribute in self.message.attributes { - let attributeEncoder = PostboxEncoder() - attributeEncoder.encodeRootObject(attribute) - attributesData.append(attributeEncoder.makeData()) - } - try container.encode(attributesData, forKey: "attributes") - - try container.encode(self.message.text, forKey: "text") - } - } - public let id: Int32 public let shortcut: String - public let messages: [EngineMessage] - public init(id: Int32, shortcut: String, messages: [EngineMessage]) { + public init(id: Int32, shortcut: String) { self.id = id self.shortcut = shortcut - self.messages = messages } public static func ==(lhs: QuickReplyMessageShortcut, rhs: QuickReplyMessageShortcut) -> Bool { @@ -97,9 +20,6 @@ public final class QuickReplyMessageShortcut: Codable, Equatable { if lhs.shortcut != rhs.shortcut { return false } - if lhs.messages != rhs.messages { - return false - } return true } @@ -108,7 +28,6 @@ public final class QuickReplyMessageShortcut: Codable, Equatable { self.id = try container.decode(Int32.self, forKey: "id") self.shortcut = try container.decode(String.self, forKey: "shortcut") - self.messages = try container.decode([CodableMessage].self, forKey: "messages").map { EngineMessage($0.message) } } public func encode(to encoder: Encoder) throws { @@ -116,42 +35,330 @@ public final class QuickReplyMessageShortcut: Codable, Equatable { try container.encode(self.id, forKey: "id") try container.encode(self.shortcut, forKey: "shortcut") - try container.encode(self.messages.map { CodableMessage(message: $0._asMessage()) }, forKey: "messages") } } -public final class QuickReplyMessageShortcutsState: Codable, Equatable { - public let shortcuts: [QuickReplyMessageShortcut] +struct QuickReplyMessageShortcutsState: Codable, Equatable { + var shortcuts: [QuickReplyMessageShortcut] - public init(shortcuts: [QuickReplyMessageShortcut]) { + init(shortcuts: [QuickReplyMessageShortcut]) { self.shortcuts = shortcuts } +} + +public final class ShortcutMessageList: Equatable { + public final class Item: Equatable { + public let id: Int32 + public let shortcut: String + public let topMessage: EngineMessage + public let totalCount: Int + + public init(id: Int32, shortcut: String, topMessage: EngineMessage, totalCount: Int) { + self.id = id + self.shortcut = shortcut + self.topMessage = topMessage + self.totalCount = totalCount + } + + public static func ==(lhs: Item, rhs: Item) -> Bool { + if lhs === rhs { + return true + } + if lhs.id != rhs.id { + return false + } + if lhs.shortcut != rhs.shortcut { + return false + } + if lhs.topMessage != rhs.topMessage { + return false + } + if lhs.totalCount != rhs.totalCount { + return false + } + return true + } + } - public static func ==(lhs: QuickReplyMessageShortcutsState, rhs: QuickReplyMessageShortcutsState) -> Bool { - if lhs.shortcuts != rhs.shortcuts { + public let items: [Item] + public let isLoading: Bool + + public init(items: [Item], isLoading: Bool) { + self.items = items + self.isLoading = isLoading + } + + public static func ==(lhs: ShortcutMessageList, rhs: ShortcutMessageList) -> Bool { + if lhs === rhs { + return true + } + if lhs.items != rhs.items { + return false + } + if lhs.isLoading != rhs.isLoading { return false } return true } } -func _internal_shortcutMessages(account: Account) -> Signal { +func _internal_quickReplyMessageShortcutsState(account: Account) -> Signal { let viewKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.shortcutMessages()])) return account.postbox.combinedView(keys: [viewKey]) - |> map { views -> QuickReplyMessageShortcutsState in + |> map { views -> QuickReplyMessageShortcutsState? in guard let view = views.views[viewKey] as? PreferencesView else { - return QuickReplyMessageShortcutsState(shortcuts: []) + return nil } guard let value = view.values[PreferencesKeys.shortcutMessages()]?.get(QuickReplyMessageShortcutsState.self) else { - return QuickReplyMessageShortcutsState(shortcuts: []) + return nil } return value } } -func _internal_updateShortcutMessages(account: Account, state: QuickReplyMessageShortcutsState) -> Signal { - return account.postbox.transaction { transaction -> Void in +func _internal_keepShortcutMessagesUpdated(account: Account) -> Signal { + let updateSignal = _internal_shortcutMessageList(account: account) + |> take(1) + |> mapToSignal { list -> Signal in + var acc: UInt64 = 0 + for item in list.items { + combineInt64Hash(&acc, with: UInt64(item.id)) + combineInt64Hash(&acc, with: md5StringHash(item.shortcut)) + combineInt64Hash(&acc, with: UInt64(item.topMessage.id.id)) + + var editTimestamp: Int32 = 0 + inner: for attribute in item.topMessage.attributes { + if let attribute = attribute as? EditedMessageAttribute { + editTimestamp = attribute.date + break inner + } + } + combineInt64Hash(&acc, with: UInt64(editTimestamp)) + } + + return account.network.request(Api.functions.messages.getQuickReplies(hash: finalizeInt64Hash(acc))) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + guard let result else { + return .complete() + } + + return account.postbox.transaction { transaction in + var state = transaction.getPreferencesEntry(key: PreferencesKeys.shortcutMessages())?.get(QuickReplyMessageShortcutsState.self) ?? QuickReplyMessageShortcutsState(shortcuts: []) + switch result { + case let .quickReplies(quickReplies, messages, chats, users): + let previousShortcuts = state.shortcuts + state.shortcuts.removeAll() + + let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users) + updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers) + + var storeMessages: [StoreMessage] = [] + + for message in messages { + if let message = StoreMessage(apiMessage: message, accountPeerId: account.peerId, peerIsForum: false) { + storeMessages.append(message) + } + } + let _ = transaction.addMessages(storeMessages, location: .Random) + var topMessageIds: [Int32: Int32] = [:] + + for quickReply in quickReplies { + switch quickReply { + case let .quickReply(shortcutId, shortcut, topMessage, _): + state.shortcuts.append(QuickReplyMessageShortcut( + id: shortcutId, + shortcut: shortcut + )) + topMessageIds[shortcutId] = topMessage + } + } + + if previousShortcuts != state.shortcuts { + for shortcut in previousShortcuts { + if let topMessageId = topMessageIds[shortcut.id] { + //TODO:remove earlier + let _ = topMessageId + } else { + let existingCloudMessages = transaction.getMessagesWithThreadId(peerId: account.peerId, namespace: Namespaces.Message.QuickReplyCloud, threadId: Int64(shortcut.id), from: MessageIndex.lowerBound(peerId: account.peerId, namespace: Namespaces.Message.QuickReplyCloud), includeFrom: false, to: MessageIndex.upperBound(peerId: account.peerId, namespace: Namespaces.Message.QuickReplyCloud), limit: 1000) + let existingLocalMessages = transaction.getMessagesWithThreadId(peerId: account.peerId, namespace: Namespaces.Message.QuickReplyLocal, threadId: Int64(shortcut.id), from: MessageIndex.lowerBound(peerId: account.peerId, namespace: Namespaces.Message.QuickReplyLocal), includeFrom: false, to: MessageIndex.upperBound(peerId: account.peerId, namespace: Namespaces.Message.QuickReplyLocal), limit: 1000) + + transaction.deleteMessages(existingCloudMessages.map(\.id), forEachMedia: nil) + transaction.deleteMessages(existingLocalMessages.map(\.id), forEachMedia: nil) + } + } + } + case .quickRepliesNotModified: + break + } + + transaction.setPreferencesEntry(key: PreferencesKeys.shortcutMessages(), value: PreferencesEntry(state)) + } + |> ignoreValues + } + } + + return updateSignal +} + +func _internal_shortcutMessageList(account: Account) -> Signal { + return _internal_quickReplyMessageShortcutsState(account: account) + |> distinctUntilChanged + |> mapToSignal { state -> Signal in + guard let state else { + return .single(ShortcutMessageList(items: [], isLoading: true)) + } + + var keys: [PostboxViewKey] = [] + var historyViewKeys: [Int32: PostboxViewKey] = [:] + var summaryKeys: [Int32: PostboxViewKey] = [:] + for shortcut in state.shortcuts { + let historyViewKey: PostboxViewKey = .historyView(PostboxViewKey.HistoryView( + peerId: account.peerId, + threadId: Int64(shortcut.id), + clipHoles: false, + trackHoles: false, + anchor: .lowerBound, + appendMessagesFromTheSameGroup: false, + namespaces: .just(Set([Namespaces.Message.QuickReplyCloud])), + count: 10 + )) + historyViewKeys[shortcut.id] = historyViewKey + keys.append(historyViewKey) + + let summaryKey: PostboxViewKey = .historyTagSummaryView(tag: [], peerId: account.peerId, threadId: Int64(shortcut.id), namespace: Namespaces.Message.ScheduledCloud, customTag: nil) + summaryKeys[shortcut.id] = summaryKey + keys.append(summaryKey) + } + return account.postbox.combinedView( + keys: keys + ) + |> map { views -> ShortcutMessageList in + var items: [ShortcutMessageList.Item] = [] + for shortcut in state.shortcuts { + guard let historyViewKey = historyViewKeys[shortcut.id], let historyView = views.views[historyViewKey] as? MessageHistoryView else { + continue + } + + var totalCount = 1 + if let summaryKey = summaryKeys[shortcut.id], let summaryView = views.views[summaryKey] as? MessageHistoryTagSummaryView { + if let count = summaryView.count { + totalCount = max(1, Int(count)) + } + } + + if let entry = historyView.entries.first { + items.append(ShortcutMessageList.Item(id: shortcut.id, shortcut: shortcut.shortcut, topMessage: EngineMessage(entry.message), totalCount: totalCount)) + } + } + return ShortcutMessageList(items: items, isLoading: false) + } + |> distinctUntilChanged + } +} + +func _internal_editMessageShortcut(account: Account, id: Int32, shortcut: String) -> Signal { + let remoteApply = account.network.request(Api.functions.messages.editQuickReplyShortcut(shortcutId: id, shortcut: shortcut)) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> mapToSignal { _ -> Signal in + return .complete() + } + + return account.postbox.transaction { transaction in + var state = transaction.getPreferencesEntry(key: PreferencesKeys.shortcutMessages())?.get(QuickReplyMessageShortcutsState.self) ?? QuickReplyMessageShortcutsState(shortcuts: []) + if let index = state.shortcuts.firstIndex(where: { $0.id == id }) { + state.shortcuts[index] = QuickReplyMessageShortcut(id: id, shortcut: shortcut) + } transaction.setPreferencesEntry(key: PreferencesKeys.shortcutMessages(), value: PreferencesEntry(state)) } |> ignoreValues + |> then(remoteApply) +} + +func _internal_deleteMessageShortcuts(account: Account, ids: [Int32]) -> Signal { + return account.postbox.transaction { transaction in + var state = transaction.getPreferencesEntry(key: PreferencesKeys.shortcutMessages())?.get(QuickReplyMessageShortcutsState.self) ?? QuickReplyMessageShortcutsState(shortcuts: []) + + for id in ids { + if let index = state.shortcuts.firstIndex(where: { $0.id == id }) { + state.shortcuts.remove(at: index) + } + } + transaction.setPreferencesEntry(key: PreferencesKeys.shortcutMessages(), value: PreferencesEntry(state)) + + for id in ids { + cloudChatAddClearHistoryOperation(transaction: transaction, peerId: account.peerId, threadId: Int64(id), explicitTopMessageId: nil, minTimestamp: nil, maxTimestamp: nil, type: .quickReplyMessages) + } + } + |> ignoreValues +} + +func _internal_reorderMessageShortcuts(account: Account, ids: [Int32], localCompletion: @escaping () -> Void) -> Signal { + let remoteApply = account.network.request(Api.functions.messages.reorderQuickReplies(order: ids)) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> mapToSignal { _ -> Signal in + return .complete() + } + + return account.postbox.transaction { transaction in + var state = transaction.getPreferencesEntry(key: PreferencesKeys.shortcutMessages())?.get(QuickReplyMessageShortcutsState.self) ?? QuickReplyMessageShortcutsState(shortcuts: []) + + let previousShortcuts = state.shortcuts + state.shortcuts.removeAll() + for id in ids { + if let index = previousShortcuts.firstIndex(where: { $0.id == id }) { + state.shortcuts.append(previousShortcuts[index]) + } + } + for shortcut in previousShortcuts { + if !state.shortcuts.contains(where: { $0.id == shortcut.id }) { + state.shortcuts.append(shortcut) + } + } + + transaction.setPreferencesEntry(key: PreferencesKeys.shortcutMessages(), value: PreferencesEntry(state)) + } + |> ignoreValues + |> afterCompleted { + localCompletion() + } + |> then(remoteApply) +} + +func _internal_sendMessageShortcut(account: Account, peerId: PeerId, id: Int32) -> Signal { + return account.postbox.transaction { transaction -> Peer? in + return transaction.getPeer(peerId) + } + |> mapToSignal { peer -> Signal in + guard let peer, let inputPeer = apiInputPeer(peer) else { + return .complete() + } + return account.network.request(Api.functions.messages.sendQuickReplyMessages(peer: inputPeer, shortcutId: id)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + if let result { + account.stateManager.addUpdates(result) + } + return .complete() + } + } +} + +func _internal_applySentQuickReplyMessage(transaction: Transaction, shortcut: String, quickReplyId: Int32) { + var state = transaction.getPreferencesEntry(key: PreferencesKeys.shortcutMessages())?.get(QuickReplyMessageShortcutsState.self) ?? QuickReplyMessageShortcutsState(shortcuts: []) + + if !state.shortcuts.contains(where: { $0.id == quickReplyId }) { + state.shortcuts.insert(QuickReplyMessageShortcut(id: quickReplyId, shortcut: shortcut), at: 0) + transaction.setPreferencesEntry(key: PreferencesKeys.shortcutMessages(), value: PreferencesEntry(state)) + } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift index 0695e05c0c..4c57ab1f13 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift @@ -843,7 +843,7 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa count: 40, clipHoles: true, anchor: inputAnchor, - namespaces: .not(Namespaces.Message.allScheduled) + namespaces: .not(Namespaces.Message.allNonRegular) ) if !testView.isLoading || transaction.getMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id)) != nil { let initialAnchor: ChatReplyThreadMessage.Anchor diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift index 409791f28d..d64c77acc5 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift @@ -584,12 +584,18 @@ func _internal_downloadMessage(accountPeerId: PeerId, postbox: Postbox, network: } func fetchRemoteMessage(accountPeerId: PeerId, postbox: Postbox, source: FetchMessageHistoryHoleSource, message: MessageReference) -> Signal { - guard case let .message(peer, _, id, _, _, _) = message.content else { + guard case let .message(peer, _, id, _, _, _, threadId) = message.content else { return .single(nil) } let signal: Signal if id.namespace == Namespaces.Message.ScheduledCloud { signal = source.request(Api.functions.messages.getScheduledMessages(peer: peer.inputPeer, id: [id.id])) + } else if id.namespace == Namespaces.Message.QuickReplyCloud { + if let threadId { + signal = source.request(Api.functions.messages.getQuickReplyMessages(flags: 1 << 0, shortcutId: Int32(clamping: threadId), id: [id.id], hash: 0)) + } else { + signal = .never() + } } else if id.peerId.namespace == Namespaces.Peer.CloudChannel { if let channel = peer.inputChannel { signal = source.request(Api.functions.channels.getMessages(channel: channel, id: [Api.InputMessage.inputMessageID(id: id.id)])) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift index 5f39622ea4..f6f258c154 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift @@ -192,7 +192,7 @@ public final class SparseMessageList { let location: ChatLocationInput = .peer(peerId: self.peerId, threadId: self.threadId) - self.topItemsDisposable.set((self.account.postbox.aroundMessageHistoryViewForLocation(location, anchor: .upperBound, ignoreMessagesInTimestampRange: nil, count: count, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tag: .tag(self.messageTag), appendMessagesFromTheSameGroup: false, namespaces: .not(Set(Namespaces.Message.allScheduled)), orderStatistics: []) + self.topItemsDisposable.set((self.account.postbox.aroundMessageHistoryViewForLocation(location, anchor: .upperBound, ignoreMessagesInTimestampRange: nil, count: count, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tag: .tag(self.messageTag), appendMessagesFromTheSameGroup: false, namespaces: .not(Namespaces.Message.allNonRegular), orderStatistics: []) |> deliverOn(self.queue)).start(next: { [weak self] view, updateType, _ in guard let strongSelf = self else { return diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 0d1e3ea5d7..5f625b1e91 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -1082,7 +1082,7 @@ public extension TelegramEngine { transaction.setMessageHistoryThreadInfo(peerId: id, threadId: threadId, info: nil) - _internal_clearHistory(transaction: transaction, mediaBox: self.account.postbox.mediaBox, peerId: id, threadId: threadId, namespaces: .not(Namespaces.Message.allScheduled)) + _internal_clearHistory(transaction: transaction, mediaBox: self.account.postbox.mediaBox, peerId: id, threadId: threadId, namespaces: .not(Namespaces.Message.allNonRegular)) } |> ignoreValues } @@ -1094,7 +1094,7 @@ public extension TelegramEngine { transaction.setMessageHistoryThreadInfo(peerId: id, threadId: threadId, info: nil) - _internal_clearHistory(transaction: transaction, mediaBox: self.account.postbox.mediaBox, peerId: id, threadId: threadId, namespaces: .not(Namespaces.Message.allScheduled)) + _internal_clearHistory(transaction: transaction, mediaBox: self.account.postbox.mediaBox, peerId: id, threadId: threadId, namespaces: .not(Namespaces.Message.allNonRegular)) } } |> ignoreValues @@ -1373,7 +1373,7 @@ public extension TelegramEngine { return .single(false) } - return self.account.postbox.aroundMessageHistoryViewForLocation(.peer(peerId: id, threadId: nil), anchor: .upperBound, ignoreMessagesInTimestampRange: nil, count: 44, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: []) + return self.account.postbox.aroundMessageHistoryViewForLocation(.peer(peerId: id, threadId: nil), anchor: .upperBound, ignoreMessagesInTimestampRange: nil, count: 44, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .not(Namespaces.Message.allNonRegular), orderStatistics: []) |> map { view -> Bool in for entry in view.0.entries { if entry.message.flags.contains(.Incoming) { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index eced71f7af..0cd5c6b4a1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -216,7 +216,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee let _ = accountUser switch fullUser { - case let .userFull(_, _, _, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .userFull(_, _, _, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: userFullNotifySettings)]) } @@ -228,7 +228,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee previous = CachedUserData() } switch fullUser { - case let .userFull(userFullFlags, _, _, userFullAbout, userFullSettings, personalPhoto, profilePhoto, fallbackPhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullThemeEmoticon, _, _, _, userPremiumGiftOptions, userWallpaper, stories, businessWorkHours, businessLocation): + case let .userFull(userFullFlags, _, _, userFullAbout, userFullSettings, personalPhoto, profilePhoto, fallbackPhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullThemeEmoticon, _, _, _, userPremiumGiftOptions, userWallpaper, stories, businessWorkHours, businessLocation, _, _): let _ = stories let botInfo = userFullBotInfo.flatMap(BotInfo.init(apiBotInfo:)) let isBlocked = (userFullFlags & (1 << 0)) != 0 diff --git a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift index 22173d47cb..53f630c40d 100644 --- a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift @@ -181,6 +181,36 @@ func messagesIdsGroupedByPeerId(_ ids: ReferencedReplyMessageIds) -> [PeerId: Re return dict } +func messagesIdsGroupedByPeerId(_ ids: Set) -> [PeerAndThreadId: [MessageId]] { + var dict: [PeerAndThreadId: [MessageId]] = [:] + + for id in ids { + let peerAndThreadId = PeerAndThreadId(peerId: id.messageId.peerId, threadId: id.threadId) + if dict[peerAndThreadId] == nil { + dict[peerAndThreadId] = [id.messageId] + } else { + dict[peerAndThreadId]!.append(id.messageId) + } + } + + return dict +} + +func messagesIdsGroupedByPeerId(_ ids: [MessageAndThreadId]) -> [PeerAndThreadId: [MessageId]] { + var dict: [PeerAndThreadId: [MessageId]] = [:] + + for id in ids { + let peerAndThreadId = PeerAndThreadId(peerId: id.messageId.peerId, threadId: id.threadId) + if dict[peerAndThreadId] == nil { + dict[peerAndThreadId] = [id.messageId] + } else { + dict[peerAndThreadId]!.append(id.messageId) + } + } + + return dict +} + func locallyRenderedMessage(message: StoreMessage, peers: [PeerId: Peer], associatedThreadInfo: Message.AssociatedThreadInfo? = nil) -> Message? { guard case let .Id(id) = message.id else { return nil diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift index 29434ba731..d8859b915f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -434,10 +434,10 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { override public func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) { super.setupItem(item, synchronousLoad: synchronousLoad) - if item.message.id.namespace == Namespaces.Message.Local || item.message.id.namespace == Namespaces.Message.ScheduledLocal { + if item.message.id.namespace == Namespaces.Message.Local || item.message.id.namespace == Namespaces.Message.ScheduledLocal || item.message.id.namespace == Namespaces.Message.QuickReplyLocal { self.wasPending = true } - if self.wasPending && (item.message.id.namespace != Namespaces.Message.Local && item.message.id.namespace != Namespaces.Message.ScheduledLocal) { + if self.wasPending && (item.message.id.namespace != Namespaces.Message.Local && item.message.id.namespace != Namespaces.Message.ScheduledLocal && item.message.id.namespace != Namespaces.Message.QuickReplyLocal) { self.didChangeFromPendingToSent = true } @@ -857,7 +857,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var needsShareButton = false if case .pinnedMessages = item.associatedData.subject { needsShareButton = true - } else if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) { + } else if isFailed || Namespaces.Message.allNonRegular.contains(item.message.id.namespace) { needsShareButton = false } else if item.message.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) { for attribute in item.content.firstMessage.attributes { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 6c49dba131..16c144e196 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -315,7 +315,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ needReactions = false } - if !isAction && !hasSeparateCommentsButton && !Namespaces.Message.allScheduled.contains(firstMessage.id.namespace) && !hideAllAdditionalInfo { + if !isAction && !hasSeparateCommentsButton && !Namespaces.Message.allNonRegular.contains(firstMessage.id.namespace) && !hideAllAdditionalInfo { if hasCommentButton(item: item) { result.append((firstMessage, ChatMessageCommentFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .footer, neighborSpacing: .default))) } @@ -1485,7 +1485,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } else if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.effectiveTopId == item.message.id { needsShareButton = false allowFullWidth = true - } else if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) { + } else if isFailed || Namespaces.Message.allNonRegular.contains(item.message.id.namespace) { needsShareButton = false } else if item.message.id.peerId == item.context.account.peerId { if let _ = sourceReference { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift index b4e50791ab..c1ba69fe70 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift @@ -99,7 +99,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco self.interactiveVideoNode.shouldOpen = { [weak self] in if let strongSelf = self { - if let item = strongSelf.item, (item.message.id.namespace == Namespaces.Message.Local || item.message.id.namespace == Namespaces.Message.ScheduledLocal) { + if let item = strongSelf.item, (item.message.id.namespace == Namespaces.Message.Local || item.message.id.namespace == Namespaces.Message.ScheduledLocal || item.message.id.namespace == Namespaces.Message.QuickReplyLocal) { return false } return !strongSelf.animatingHeight @@ -341,7 +341,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco var needsShareButton = false if case .pinnedMessages = item.associatedData.subject { needsShareButton = true - } else if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) { + } else if isFailed || Namespaces.Message.allNonRegular.contains(item.message.id.namespace) { needsShareButton = false } else if item.message.id.peerId == item.context.account.peerId { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift index 588bf59b55..e10b3d40c7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -464,7 +464,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { break } } - if item.message.id.namespace == Namespaces.Message.Local || item.message.id.namespace == Namespaces.Message.ScheduledLocal { + if item.message.id.namespace == Namespaces.Message.Local || item.message.id.namespace == Namespaces.Message.ScheduledLocal || item.message.id.namespace == Namespaces.Message.QuickReplyLocal { notConsumed = true } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift index e1f4f5b65f..297e1a4988 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift @@ -524,7 +524,7 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat return } var messageId: MessageId? - if let messageReference = messageReference, case let .message(_, _, id, _, _, _) = messageReference.content { + if let messageReference = messageReference, case let .message(_, _, id, _, _, _, _) = messageReference.content { messageId = id } strongSelf.controllerInteraction?.openPeerContextMenu(peer, messageId, strongSelf.containerNode, strongSelf.containerNode.bounds, gesture) @@ -751,7 +751,7 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat @objc private func tapGesture(_ recognizer: ListViewTapGestureRecognizer) { if case .ended = recognizer.state { - if self.peerId.namespace == Namespaces.Peer.Empty, case let .message(_, _, id, _, _, _) = self.messageReference?.content { + if self.peerId.namespace == Namespaces.Peer.Empty, case let .message(_, _, id, _, _, _, _) = self.messageReference?.content { self.controllerInteraction?.displayMessageTooltip(id, self.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, self.avatarNode.frame) } else if let peer = self.peer { if let adMessageId = self.adMessageId { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift index 28f52c83e9..940ca9feb7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift @@ -494,7 +494,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode { let shouldHaveRadioNode = optionResult == nil let isSelectable: Bool - if shouldHaveRadioNode, case .poll(multipleAnswers: true) = poll.kind, !Namespaces.Message.allScheduled.contains(message.id.namespace) { + if shouldHaveRadioNode, case .poll(multipleAnswers: true) = poll.kind, !Namespaces.Message.allNonRegular.contains(message.id.namespace) { isSelectable = true } else { isSelectable = false @@ -905,7 +905,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } } if !hasSelection { - if !Namespaces.Message.allScheduled.contains(item.message.id.namespace) { + if !Namespaces.Message.allNonRegular.contains(item.message.id.namespace) { item.controllerInteraction.requestOpenMessagePollResults(item.message.id, pollId) } } else if !selectedOpaqueIdentifiers.isEmpty { @@ -1211,7 +1211,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { boundingSize.width = max(boundingSize.width, min(270.0, constrainedSize.width)) var canVote = false - if (item.message.id.namespace == Namespaces.Message.Cloud || Namespaces.Message.allScheduled.contains(item.message.id.namespace)), let poll = poll, poll.pollId.namespace == Namespaces.Media.CloudPoll, !isClosed { + if (item.message.id.namespace == Namespaces.Message.Cloud || Namespaces.Message.allNonRegular.contains(item.message.id.namespace)), let poll = poll, poll.pollId.namespace == Namespaces.Media.CloudPoll, !isClosed { var hasVoted = false if let voters = poll.results.voters { for voter in voters { @@ -1580,7 +1580,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { self.buttonNode.isHidden = false } - if Namespaces.Message.allScheduled.contains(item.message.id.namespace) { + if Namespaces.Message.allNonRegular.contains(item.message.id.namespace) { self.buttonNode.isUserInteractionEnabled = false } else { self.buttonNode.isUserInteractionEnabled = true @@ -1643,7 +1643,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { if optionNode.frame.contains(point), case .tap = gesture { if optionNode.isUserInteractionEnabled { return ChatMessageBubbleContentTapAction(content: .ignore) - } else if let result = optionNode.currentResult, let item = self.item, !Namespaces.Message.allScheduled.contains(item.message.id.namespace), let poll = self.poll, let option = optionNode.option, !isBotChat { + } else if let result = optionNode.currentResult, let item = self.item, !Namespaces.Message.allNonRegular.contains(item.message.id.namespace), let poll = self.poll, let option = optionNode.option, !isBotChat { switch poll.publicity { case .anonymous: let string: String diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index 35dc348dac..3c6f2d43f0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -499,7 +499,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { var needsShareButton = false if case .pinnedMessages = item.associatedData.subject { needsShareButton = true - } else if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) { + } else if isFailed || Namespaces.Message.allNonRegular.contains(item.message.id.namespace) { needsShareButton = false } else if item.message.id.peerId == item.context.account.peerId { for attribute in item.content.firstMessage.attributes { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenBusinessHoursItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenBusinessHoursItem.swift index 0323734314..6ee2fe4f8f 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenBusinessHoursItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenBusinessHoursItem.swift @@ -265,12 +265,12 @@ private final class PeerInfoScreenBusinessHoursItemNode: PeerInfoScreenItemNode for range in self.cachedWeekMinuteSet.rangeView { if range.lowerBound > currentWeekMinute { let openInMinutes = range.lowerBound - currentWeekMinute - let _ = openInMinutes - /*if openInMinutes < 60 { - openStatusText = "Opens in \(openInMinutes) minutes" + //TODO:localize + if openInMinutes < 60 { + currentDayStatusText = "Opens in \(openInMinutes) minutes" } else if openInMinutes < 6 * 60 { - openStatusText = "Opens in \(openInMinutes / 60) hours" - } else*/ do { + currentDayStatusText = "Opens in \(openInMinutes / 60) hours" + } else { let openDate = currentDate.addingTimeInterval(Double(openInMinutes * 60)) let openTimestamp = Int32(openDate.timeIntervalSince1970) + Int32(currentCalendar.timeZone.secondsFromGMT() - TimeZone.current.secondsFromGMT()) diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift index b238231a04..8e5298fe9b 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift @@ -6,173 +6,168 @@ import TelegramCore import AccountContext final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtocol { + private final class PendingMessageContext { + let disposable = MetaDisposable() + var message: Message? + + init() { + } + } + private final class Impl { let queue: Queue let context: AccountContext - private var messages: [Message] = [] - private var nextMessageId: Int32 = 1000 - let messagesPromise = Promise<[Message]>([]) + private var shortcut: String + private var shortcutId: Int32? - private var nextGroupingId: UInt32 = 0 - private var groupingKeyToGroupId: [Int64: UInt32] = [:] + private(set) var mergedHistoryView: MessageHistoryView? + private var sourceHistoryView: MessageHistoryView? - init(queue: Queue, context: AccountContext, messages: [EngineMessage]) { + private var pendingMessages: [PendingMessageContext] = [] + private var historyViewDisposable: Disposable? + let historyViewStream = ValuePipe<(MessageHistoryView, ViewUpdateType)>() + private var nextUpdateIsHoleFill: Bool = false + + init(queue: Queue, context: AccountContext, shortcut: String, shortcutId: Int32?) { self.queue = queue self.context = context - self.messages = messages.map { $0._asMessage() } - self.notifyMessagesUpdated() + self.shortcut = shortcut + self.shortcutId = shortcutId - if let maxMessageId = messages.map(\.id).max() { - self.nextMessageId = maxMessageId.id + 1 - } - if let maxGroupingId = messages.compactMap(\.groupInfo?.stableId).max() { - self.nextGroupingId = maxGroupingId + 1 - } + self.updateHistoryViewRequest(reload: false) } deinit { + for context in self.pendingMessages { + context.disposable.dispose() + } + self.historyViewDisposable?.dispose() } - private func notifyMessagesUpdated() { - self.messages.sort(by: { $0.index > $1.index }) - self.messagesPromise.set(.single(self.messages)) + private func updateHistoryViewRequest(reload: Bool) { + if let shortcutId = self.shortcutId { + if self.historyViewDisposable == nil || reload { + self.historyViewDisposable?.dispose() + + self.historyViewDisposable = (self.context.account.viewTracker.quickReplyMessagesViewForLocation(quickReplyId: shortcutId) + |> deliverOn(self.queue)).start(next: { [weak self] view, update, _ in + guard let self else { + return + } + if update == .FillHole { + self.nextUpdateIsHoleFill = true + self.updateHistoryViewRequest(reload: true) + return + } + + let nextUpdateIsHoleFill = self.nextUpdateIsHoleFill + self.nextUpdateIsHoleFill = false + + self.sourceHistoryView = view + self.updateHistoryView(updateType: nextUpdateIsHoleFill ? .FillHole : .Generic) + }) + } + } else { + if self.sourceHistoryView == nil { + let sourceHistoryView = MessageHistoryView(tag: nil, namespaces: .just(Namespaces.Message.allQuickReply), entries: [], holeEarlier: false, holeLater: false, isLoading: false) + self.sourceHistoryView = sourceHistoryView + self.updateHistoryView(updateType: .Initial) + } + } + } + + private func updateHistoryView(updateType: ViewUpdateType) { + var entries = self.sourceHistoryView?.entries ?? [] + for pendingMessage in self.pendingMessages { + if let message = pendingMessage.message { + if !entries.contains(where: { $0.message.stableId == message.stableId }) { + entries.append(MessageHistoryEntry( + message: message, + isRead: true, + location: nil, + monthLocation: nil, + attributes: MutableMessageHistoryEntryAttributes( + authorIsContact: false + ) + )) + } + } + } + entries.sort(by: { $0.message.index < $1.message.index }) + + let mergedHistoryView = MessageHistoryView(tag: nil, namespaces: .just(Namespaces.Message.allQuickReply), entries: entries, holeEarlier: false, holeLater: false, isLoading: false) + self.mergedHistoryView = mergedHistoryView + + self.historyViewStream.putNext((mergedHistoryView, updateType)) } func enqueueMessages(messages: [EnqueueMessage]) { - for message in messages { - switch message { - case let .message(text, attributes, _, mediaReference, _, _, _, localGroupingKey, correlationId, _): - let _ = attributes - let _ = mediaReference - let _ = correlationId - - let messageId = self.nextMessageId - self.nextMessageId += 1 - - var attributes: [MessageAttribute] = [] - attributes.append(OutgoingMessageInfoAttribute( - uniqueId: Int64.random(in: Int64.min ... Int64.max), - flags: [], - acknowledged: true, - correlationId: correlationId, - bubbleUpEmojiOrStickersets: [] - )) - - var media: [Media] = [] - if let mediaReference { - media.append(mediaReference.media) - } - - let mappedMessage = Message( - stableId: UInt32(messageId), - stableVersion: 0, - id: MessageId( - peerId: PeerId(namespace: PeerId.Namespace._internalFromInt32Value(0), id: PeerId.Id._internalFromInt64Value(0)), - namespace: Namespaces.Message.Local, - id: Int32(messageId) - ), - globallyUniqueId: nil, - groupingKey: localGroupingKey, - groupInfo: localGroupingKey.flatMap { value in - if let current = self.groupingKeyToGroupId[value] { - return MessageGroupInfo(stableId: current) - } else { - let groupId = self.nextGroupingId - self.nextGroupingId += 1 - self.groupingKeyToGroupId[value] = groupId - return MessageGroupInfo(stableId: groupId) - } - }, - threadId: nil, - timestamp: messageId, - flags: [], - tags: [], - globalTags: [], - localTags: [], - customTags: [], - forwardInfo: nil, - author: nil, - text: text, - attributes: attributes, - media: media, - peers: SimpleDictionary(), - associatedMessages: SimpleDictionary(), - associatedMessageIds: [], - associatedMedia: [:], - associatedThreadInfo: nil, - associatedStories: [:] - ) - self.messages.append(mappedMessage) - case .forward: - break + let threadId = self.shortcutId.flatMap(Int64.init) + let _ = (TelegramCore.enqueueMessages(account: self.context.account, peerId: self.context.account.peerId, messages: messages.map { message in + return message.withUpdatedThreadId(threadId).withUpdatedAttributes { attributes in + var attributes = attributes + attributes.removeAll(where: { $0 is OutgoingQuickReplyMessageAttribute }) + attributes.append(OutgoingQuickReplyMessageAttribute(shortcut: self.shortcut)) + return attributes } - } - self.notifyMessagesUpdated() + }) + |> deliverOn(self.queue)).startStandalone(next: { [weak self] result in + guard let self else { + return + } + if self.shortcutId != nil { + return + } + for id in result { + if let id { + let pendingMessage = PendingMessageContext() + self.pendingMessages.append(pendingMessage) + pendingMessage.disposable.set(( + self.context.account.postbox.messageView(id) + |> deliverOn(self.queue) + ).startStrict(next: { [weak self, weak pendingMessage] messageView in + guard let self else { + return + } + guard let pendingMessage else { + return + } + pendingMessage.message = messageView.message + if let message = pendingMessage.message, message.id.namespace == Namespaces.Message.QuickReplyCloud, let threadId = message.threadId { + self.shortcutId = Int32(clamping: threadId) + self.updateHistoryViewRequest(reload: true) + } else { + self.updateHistoryView(updateType: .Generic) + } + })) + } + } + }) } func deleteMessages(ids: [EngineMessage.Id]) { - self.messages = self.messages.filter({ !ids.contains($0.id) }) - self.notifyMessagesUpdated() + let _ = self.context.engine.messages.deleteMessagesInteractively(messageIds: ids, type: .forEveryone).startStandalone() } func editMessage(id: EngineMessage.Id, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool) { - guard let index = self.messages.firstIndex(where: { $0.id == id }) else { - return + } + + func quickReplyUpdateShortcut(value: String) { + if let shortcutId = self.shortcutId { + self.context.engine.accountData.editMessageShortcut(id: shortcutId, shortcut: value) } - let originalMessage = self.messages[index] - - var mappedMedia = originalMessage.media - switch media { - case .keep: - break - case let .update(value): - mappedMedia = [value.media] - } - - var mappedAtrributes = originalMessage.attributes - mappedAtrributes.removeAll(where: { $0 is TextEntitiesMessageAttribute }) - if let entities { - mappedAtrributes.append(entities) - } - - let mappedMessage = Message( - stableId: originalMessage.stableId, - stableVersion: originalMessage.stableVersion + 1, - id: originalMessage.id, - globallyUniqueId: originalMessage.globallyUniqueId, - groupingKey: originalMessage.groupingKey, - groupInfo: originalMessage.groupInfo, - threadId: originalMessage.threadId, - timestamp: originalMessage.timestamp, - flags: originalMessage.flags, - tags: originalMessage.tags, - globalTags: originalMessage.globalTags, - localTags: originalMessage.localTags, - customTags: originalMessage.customTags, - forwardInfo: originalMessage.forwardInfo, - author: originalMessage.author, - text: text, - attributes: mappedAtrributes, - media: mappedMedia, - peers: originalMessage.peers, - associatedMessages: originalMessage.associatedMessages, - associatedMessageIds: originalMessage.associatedMessageIds, - associatedMedia: originalMessage.associatedMedia, - associatedThreadInfo: originalMessage.associatedThreadInfo, - associatedStories: originalMessage.associatedStories - ) - - self.messages[index] = mappedMessage - self.notifyMessagesUpdated() } } var kind: ChatCustomContentsKind - var messages: Signal<[Message], NoError> { + var historyView: Signal<(MessageHistoryView, ViewUpdateType), NoError> { return self.impl.signalWith({ impl, subscriber in - return impl.messagesPromise.get().start(next: subscriber.putNext) + if let mergedHistoryView = impl.mergedHistoryView { + subscriber.putNext((mergedHistoryView, .Initial)) + } + return impl.historyViewStream.signal().start(next: subscriber.putNext) }) } @@ -183,13 +178,23 @@ final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtoco private let queue: Queue private let impl: QueueLocalObject - init(context: AccountContext, messages: [EngineMessage], kind: ChatCustomContentsKind) { + init(context: AccountContext, kind: ChatCustomContentsKind, shortcutId: Int32?) { self.kind = kind + let initialShortcut: String + switch kind { + case .awayMessageInput: + initialShortcut = "_away" + case .greetingMessageInput: + initialShortcut = "_greeting" + case let .quickReplyMessageInput(shortcut): + initialShortcut = shortcut + } + let queue = Queue() self.queue = queue self.impl = QueueLocalObject(queue: queue, generate: { - return Impl(queue: queue, context: context, messages: messages) + return Impl(queue: queue, context: context, shortcut: initialShortcut, shortcutId: shortcutId) }) } @@ -213,5 +218,8 @@ final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtoco func quickReplyUpdateShortcut(value: String) { self.kind = .quickReplyMessageInput(shortcut: value) + self.impl.with { impl in + impl.quickReplyUpdateShortcut(value: value) + } } } diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift index 27e2488031..409efe246f 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift @@ -352,8 +352,8 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { } let contents = AutomaticBusinessMessageSetupChatContents( context: component.context, - messages: self.messages, - kind: component.mode == .away ? .awayMessageInput : .greetingMessageInput + kind: component.mode == .away ? .awayMessageInput : .greetingMessageInput, + shortcutId: nil ) let chatController = component.context.sharedContext.makeChatController( context: component.context, @@ -365,17 +365,6 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { chatController.navigationPresentation = .modal self.environment?.controller()?.push(chatController) self.messagesDisposable?.dispose() - self.messagesDisposable = (contents.messages - |> deliverOnMainQueue).startStrict(next: { [weak self] messages in - guard let self else { - return - } - let messages = messages.map(EngineMessage.init) - if self.messages != messages { - self.messages = messages - self.state?.updated(transition: .immediate) - } - }) } private func openCustomScheduleDateSetup(isStartTime: Bool, isDate: Bool) { diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BottomPanelComponent.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BottomPanelComponent.swift new file mode 100644 index 0000000000..2dd0f3bae7 --- /dev/null +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BottomPanelComponent.swift @@ -0,0 +1,117 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramPresentationData +import ComponentDisplayAdapters + +final class BottomPanelComponent: Component { + let theme: PresentationTheme + let content: AnyComponentWithIdentity + let insets: UIEdgeInsets + + init( + theme: PresentationTheme, + content: AnyComponentWithIdentity, + insets: UIEdgeInsets + ) { + self.theme = theme + self.content = content + self.insets = insets + } + + static func ==(lhs: BottomPanelComponent, rhs: BottomPanelComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.content != rhs.content { + return false + } + if lhs.insets != rhs.insets { + return false + } + return true + } + + final class View: UIView { + private let separatorLayer: SimpleLayer + private let backgroundView: BlurredBackgroundView + private var content = ComponentView() + + private var component: BottomPanelComponent? + private weak var componentState: EmptyComponentState? + + override init(frame: CGRect) { + self.separatorLayer = SimpleLayer() + self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true) + + super.init(frame: frame) + + self.addSubview(self.backgroundView) + self.layer.addSublayer(self.separatorLayer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: BottomPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + let previousComponent = self.component + self.component = component + self.componentState = state + + let themeUpdated = previousComponent?.theme !== component.theme + + var contentHeight: CGFloat = 0.0 + + contentHeight += component.insets.top + + var contentTransition = transition + if let previousComponent, previousComponent.content.id != component.content.id { + contentTransition = contentTransition.withAnimation(.none) + self.content.view?.removeFromSuperview() + self.content = ComponentView() + } + + let contentSize = self.content.update( + transition: contentTransition, + component: component.content.component, + environment: {}, + containerSize: CGSize(width: availableSize.width - component.insets.left - component.insets.right, height: availableSize.height - component.insets.top - component.insets.bottom) + ) + let contentFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - contentSize.width) * 0.5), y: contentHeight), size: contentSize) + if let contentView = self.content.view { + if contentView.superview == nil { + self.addSubview(contentView) + } + contentTransition.setFrame(view: contentView, frame: contentFrame) + } + contentHeight += contentSize.height + + contentHeight += component.insets.bottom + + let size = CGSize(width: availableSize.width, height: contentHeight) + + if themeUpdated { + self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) + self.separatorLayer.backgroundColor = component.theme.rootController.navigationBar.separatorColor.cgColor + } + + let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size) + transition.setFrame(view: self.backgroundView, frame: backgroundFrame) + self.backgroundView.update(size: backgroundFrame.size, transition: transition.containedViewLayoutTransition) + + transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: availableSize.width, height: UIScreenPixel))) + + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplyEmptyStateComponent.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplyEmptyStateComponent.swift index d2572d0be4..a145ec7be2 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplyEmptyStateComponent.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplyEmptyStateComponent.swift @@ -7,6 +7,7 @@ import AppBundle import ButtonComponent import MultilineTextComponent import BalancedTextComponent +import LottieComponent final class QuickReplyEmptyStateComponent: Component { let theme: PresentationTheme @@ -63,72 +64,6 @@ final class QuickReplyEmptyStateComponent: Component { let _ = previousComponent - let iconTitleSpacing: CGFloat = 10.0 - let titleTextSpacing: CGFloat = 8.0 - - let iconSize = self.icon.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "📝", font: Font.semibold(90.0), textColor: component.theme.rootController.navigationBar.primaryTextColor)), - horizontalAlignment: .center - )), - environment: {}, - containerSize: CGSize(width: 100.0, height: 100.0) - ) - - //TODO:localize - let titleSize = self.title.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "No Quick Replies", font: Font.semibold(17.0), textColor: component.theme.rootController.navigationBar.primaryTextColor)), - horizontalAlignment: .center - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - 16.0 * 2.0, height: 100.0) - ) - - let textSize = self.text.update( - transition: .immediate, - component: AnyComponent(BalancedTextComponent( - text: .plain(NSAttributedString(string: "Set up shortcuts with rich text and media to respond to messages faster.", font: Font.regular(15.0), textColor: component.theme.rootController.navigationBar.secondaryTextColor)), - horizontalAlignment: .center, - maximumNumberOfLines: 20 - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - 16.0 * 2.0, height: 100.0) - ) - - let centralContentsHeight: CGFloat = iconSize.height + iconTitleSpacing + titleSize.height + titleTextSpacing - var centralContentsY: CGFloat = component.insets.top + floor((availableSize.height - component.insets.top - component.insets.bottom - centralContentsHeight) * 0.5) - - let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: centralContentsY), size: iconSize) - if let iconView = self.icon.view { - if iconView.superview == nil { - self.addSubview(iconView) - } - transition.setFrame(view: iconView, frame: iconFrame) - } - centralContentsY += iconSize.height + iconTitleSpacing - - let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: centralContentsY), size: titleSize) - if let titleView = self.title.view { - if titleView.superview == nil { - self.addSubview(titleView) - } - titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) - transition.setPosition(view: titleView, position: titleFrame.center) - } - centralContentsY += titleSize.height + titleTextSpacing - - let textFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - textSize.width) * 0.5), y: centralContentsY), size: textSize) - if let textView = self.text.view { - if textView.superview == nil { - self.addSubview(textView) - } - textView.bounds = CGRect(origin: CGPoint(), size: textFrame.size) - transition.setPosition(view: textView, position: textFrame.center) - } - let buttonSize = self.button.update( transition: transition, component: AnyComponent(ButtonComponent( @@ -159,7 +94,7 @@ final class QuickReplyEmptyStateComponent: Component { environment: {}, containerSize: CGSize(width: min(availableSize.width - 16.0 * 2.0, 280.0), height: 50.0) ) - let buttonFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - buttonSize.width) * 0.5), y: availableSize.height - component.insets.bottom - 8.0 - buttonSize.height), size: buttonSize) + let buttonFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - buttonSize.width) * 0.5), y: availableSize.height - component.insets.bottom - 14.0 - buttonSize.height), size: buttonSize) if let buttonView = self.button.view { if buttonView.superview == nil { self.addSubview(buttonView) @@ -167,6 +102,75 @@ final class QuickReplyEmptyStateComponent: Component { transition.setFrame(view: buttonView, frame: buttonFrame) } + let iconTitleSpacing: CGFloat = 13.0 + let titleTextSpacing: CGFloat = 9.0 + + let iconSize = self.icon.update( + transition: .immediate, + component: AnyComponent(LottieComponent( + content: LottieComponent.AppBundleContent(name: "WriteEmoji"), + loop: false + )), + environment: {}, + containerSize: CGSize(width: 120.0, height: 120.0) + ) + + //TODO:localize + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: "No Quick Replies", font: Font.semibold(17.0), textColor: component.theme.rootController.navigationBar.primaryTextColor)), + horizontalAlignment: .center + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - 16.0 * 2.0, height: 100.0) + ) + + let textSize = self.text.update( + transition: .immediate, + component: AnyComponent(BalancedTextComponent( + text: .plain(NSAttributedString(string: "Set up shortcuts with rich text and media to respond to messages faster.", font: Font.regular(15.0), textColor: component.theme.list.itemSecondaryTextColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 20, + lineSpacing: 0.2 + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - 16.0 * 2.0, height: 100.0) + ) + + let topInset: CGFloat = component.insets.top + + let centralContentsHeight: CGFloat = iconSize.height + iconTitleSpacing + titleSize.height + titleTextSpacing + var centralContentsY: CGFloat = topInset + floor((buttonFrame.minY - topInset - centralContentsHeight) * 0.426) + + let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: centralContentsY), size: iconSize) + if let iconView = self.icon.view { + if iconView.superview == nil { + self.addSubview(iconView) + } + transition.setFrame(view: iconView, frame: iconFrame) + } + centralContentsY += iconSize.height + iconTitleSpacing + + let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: centralContentsY), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) + transition.setPosition(view: titleView, position: titleFrame.center) + } + centralContentsY += titleSize.height + titleTextSpacing + + let textFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - textSize.width) * 0.5), y: centralContentsY), size: textSize) + if let textView = self.text.view { + if textView.superview == nil { + self.addSubview(textView) + } + textView.bounds = CGRect(origin: CGPoint(), size: textFrame.size) + transition.setPosition(view: textView, position: textFrame.center) + } + return availableSize } } diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift index 58cf348038..010f4c6fca 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift @@ -19,6 +19,8 @@ import ItemListUI import ChatListUI import QuickReplyNameAlertController import ChatListHeaderComponent +import PlainButtonComponent +import MultilineTextComponent final class QuickReplySetupScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -45,30 +47,30 @@ final class QuickReplySetupScreenComponent: Component { private enum ContentEntry: Comparable, Identifiable { enum Id: Hashable { case add - case item(String) + case item(Int32) } var stableId: Id { switch self { case .add: return .add - case let .item(item, _, _, _): - return .item(item.shortcut) + case let .item(item, _, _, _, _): + return .item(item.id) } } case add - case item(item: QuickReplyMessageShortcut, accountPeer: EnginePeer, sortIndex: Int, isEditing: Bool) + case item(item: ShortcutMessageList.Item, accountPeer: EnginePeer, sortIndex: Int, isEditing: Bool, isSelected: Bool) static func <(lhs: ContentEntry, rhs: ContentEntry) -> Bool { switch lhs { case .add: return false - case let .item(lhsItem, _, lhsSortIndex, _): + case let .item(lhsItem, _, lhsSortIndex, _, _): switch rhs { case .add: return false - case let .item(rhsItem, _, rhsSortIndex, _): + case let .item(rhsItem, _, rhsSortIndex, _, _): if lhsSortIndex != rhsSortIndex { return lhsSortIndex < rhsSortIndex } @@ -97,10 +99,10 @@ final class QuickReplySetupScreenComponent: Component { guard let listNode, let parentView = listNode.parentView else { return } - parentView.openQuickReplyChat(shortcut: nil) + parentView.openQuickReplyChat(shortcut: nil, shortcutId: nil) } ) - case let .item(item, accountPeer, _, isEditing): + case let .item(item, accountPeer, _, isEditing, isSelected): let chatListNodeInteraction = ChatListNodeInteraction( context: listNode.context, animationCache: listNode.context.animationCache, @@ -111,13 +113,21 @@ final class QuickReplySetupScreenComponent: Component { guard let listNode, let parentView = listNode.parentView else { return } - parentView.openQuickReplyChat(shortcut: item.shortcut) + parentView.openQuickReplyChat(shortcut: item.shortcut, shortcutId: item.id) }, disabledPeerSelected: { _, _, _ in }, - togglePeerSelected: { _, _ in + togglePeerSelected: { [weak listNode] _, _ in + guard let listNode, let parentView = listNode.parentView else { + return + } + parentView.toggleShortcutSelection(id: item.id) }, - togglePeersSelection: { _, _ in + togglePeersSelection: { [weak listNode] _, _ in + guard let listNode, let parentView = listNode.parentView else { + return + } + parentView.toggleShortcutSelection(id: item.id) }, additionalCategorySelected: { _ in }, @@ -125,7 +135,7 @@ final class QuickReplySetupScreenComponent: Component { guard let listNode, let parentView = listNode.parentView else { return } - parentView.openQuickReplyChat(shortcut: item.shortcut) + parentView.openQuickReplyChat(shortcut: item.shortcut, shortcutId: item.id) }, groupSelected: { _ in }, @@ -143,7 +153,7 @@ final class QuickReplySetupScreenComponent: Component { guard let listNode, let parentView = listNode.parentView else { return } - parentView.openDeleteShortcut(shortcut: item.shortcut) + parentView.openDeleteShortcuts(ids: [item.id]) }, deletePeerThread: { _, _ in }, @@ -193,7 +203,7 @@ final class QuickReplySetupScreenComponent: Component { guard let listNode, let parentView = listNode.parentView else { return } - parentView.openEditShortcut(shortcut: item.shortcut) + parentView.openEditShortcut(id: item.id, currentValue: item.shortcut) } ) @@ -215,7 +225,7 @@ final class QuickReplySetupScreenComponent: Component { filterData: nil, index: EngineChatList.Item.Index.chatList(ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: listNode.context.account.peerId, namespace: 0, id: 0), timestamp: 0))), content: .peer(ChatListItemContent.PeerData( - messages: item.messages.first.flatMap({ [$0] }) ?? [], + messages: [item.topMessage], peer: EngineRenderedPeer(peer: accountPeer), threadInfo: nil, combinedReadState: nil, @@ -245,7 +255,7 @@ final class QuickReplySetupScreenComponent: Component { )), editing: isEditing, hasActiveRevealControls: false, - selected: false, + selected: isSelected, header: nil, enableContextActions: true, hiddenOffset: false, @@ -260,6 +270,10 @@ final class QuickReplySetupScreenComponent: Component { let context: AccountContext var presentationData: PresentationData private var currentEntries: [ContentEntry] = [] + private var originalEntries: [ContentEntry] = [] + private var tempOrder: [Int32]? + private var pendingRemoveItems: [Int32]? + private var resetTempOrderOnNextUpdate: Bool = false init(parentView: View, context: AccountContext) { self.parentView = parentView @@ -267,6 +281,87 @@ final class QuickReplySetupScreenComponent: Component { self.presentationData = context.sharedContext.currentPresentationData.with({ $0 }) super.init() + + self.reorderBegan = { [weak self] in + guard let self else { + return + } + self.tempOrder = nil + } + self.reorderCompleted = { [weak self] _ in + guard let self, let tempOrder = self.tempOrder else { + return + } + self.resetTempOrderOnNextUpdate = true + self.context.engine.accountData.reorderMessageShortcuts(ids: tempOrder, completion: {}) + } + self.reorderItem = { [weak self] fromIndex, toIndex, transactionOpaqueState -> Signal in + guard let self else { + return .single(false) + } + guard fromIndex >= 0 && fromIndex < self.currentEntries.count && toIndex >= 0 && toIndex < self.currentEntries.count else { + return .single(false) + } + + let fromEntry = self.currentEntries[fromIndex] + let toEntry = self.currentEntries[toIndex] + + var referenceId: Int32? + var beforeAll = false + switch toEntry { + case let .item(item, _, _, _, _): + referenceId = item.id + case .add: + beforeAll = true + } + + if case let .item(item, _, _, _, _) = fromEntry { + var itemIds = self.currentEntries.compactMap { entry -> Int32? in + switch entry { + case .add: + return nil + case let .item(item, _, _, _, _): + return item.id + } + } + let itemId: Int32? = item.id + + if let itemId { + itemIds = itemIds.filter({ $0 != itemId }) + if let referenceId { + var inserted = false + for i in 0 ..< itemIds.count { + if itemIds[i] == referenceId { + if fromIndex < toIndex { + itemIds.insert(itemId, at: i + 1) + } else { + itemIds.insert(itemId, at: i) + } + inserted = true + break + } + } + if !inserted { + itemIds.append(itemId) + } + } else if beforeAll { + itemIds.insert(itemId, at: 0) + } else { + itemIds.append(itemId) + } + if self.tempOrder != itemIds { + self.tempOrder = itemIds + self.setEntries(entries: self.originalEntries, animated: true) + } + + return .single(true) + } else { + return .single(false) + } + } else { + return .single(false) + } + } } func update(size: CGSize, insets: UIEdgeInsets, transition: Transition) { @@ -275,14 +370,74 @@ final class QuickReplySetupScreenComponent: Component { deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], - options: [.Synchronous, .LowLatency], + options: [.Synchronous, .LowLatency, .PreferSynchronousResourceLoading], additionalScrollDistance: 0.0, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: listViewDuration, curve: listViewCurve), updateOpaqueState: nil ) } + func setPendingRemoveItems(itemIds: [Int32]) { + self.pendingRemoveItems = itemIds + self.setEntries(entries: self.originalEntries, animated: true) + } + func setEntries(entries: [ContentEntry], animated: Bool) { + if self.resetTempOrderOnNextUpdate { + self.resetTempOrderOnNextUpdate = false + self.tempOrder = nil + } + let pendingRemoveItems = self.pendingRemoveItems + self.pendingRemoveItems = nil + + self.originalEntries = entries + + var entries = entries + if let pendingRemoveItems { + entries = entries.filter { entry in + switch entry.stableId { + case .add: + return true + case let .item(id): + return !pendingRemoveItems.contains(id) + } + } + } + + if let tempOrder = self.tempOrder { + let originalList = entries + entries.removeAll() + + if let entry = originalList.first(where: { entry in + if case .add = entry { + return true + } else { + return false + } + }) { + entries.append(entry) + } + + for id in tempOrder { + if let entry = originalList.first(where: { entry in + if case let .item(listId) = entry.stableId, listId == id { + return true + } else { + return false + } + }) { + entries.append(entry) + } + } + for entry in originalList { + if !entries.contains(where: { listEntry in + listEntry.stableId == entry.stableId + }) { + entries.append(entry) + } + } + } + let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: self.currentEntries, rightList: entries) self.currentEntries = entries @@ -316,15 +471,19 @@ final class QuickReplySetupScreenComponent: Component { private let navigationBarView = ComponentView() private var navigationHeight: CGFloat? + private var selectionPanel: ComponentView? + private var isUpdating: Bool = false private var component: QuickReplySetupScreenComponent? private(set) weak var state: EmptyComponentState? private var environment: EnvironmentType? - private var items: [QuickReplyMessageShortcut] = [] - private var itemsDisposable: Disposable? - private var messagesDisposable: Disposable? + private var shortcutMessageList: ShortcutMessageList? + private var shortcutMessageListDisposable: Disposable? + private var keepUpdatedDisposable: Disposable? + + private var selectedIds = Set() private var isEditing: Bool = false private var isSearchDisplayControllerActive: Bool = false @@ -340,44 +499,27 @@ final class QuickReplySetupScreenComponent: Component { } deinit { - self.itemsDisposable?.dispose() - self.messagesDisposable?.dispose() + self.shortcutMessageListDisposable?.dispose() + self.keepUpdatedDisposable?.dispose() } func scrollToTop() { } func attemptNavigation(complete: @escaping () -> Void) -> Bool { - guard let component = self.component else { - return true - } - component.context.engine.accountData.updateShortcutMessages(state: QuickReplyMessageShortcutsState(shortcuts: self.items)) return true } - func openQuickReplyChat(shortcut: String?) { + func openQuickReplyChat(shortcut: String?, shortcutId: Int32?) { guard let component = self.component else { return } if let shortcut { - var mappedMessages: [EngineMessage] = [] - if let messages = self.items.first(where: { $0.shortcut == shortcut })?.messages { - var nextId: Int32 = 1 - for message in messages { - var mappedMessage = message._asMessage() - mappedMessage = mappedMessage.withUpdatedId(id: MessageId(peerId: component.context.account.peerId, namespace: 0, id: nextId)) - mappedMessage = mappedMessage.withUpdatedStableId(stableId: UInt32(nextId)) - mappedMessage = mappedMessage.withUpdatedTimestamp(nextId) - mappedMessages.append(EngineMessage(mappedMessage)) - - nextId += 1 - } - } let contents = AutomaticBusinessMessageSetupChatContents( context: component.context, - messages: mappedMessages, - kind: .quickReplyMessageInput(shortcut: shortcut) + kind: .quickReplyMessageInput(shortcut: shortcut), + shortcutId: shortcutId ) let chatController = component.context.sharedContext.makeChatController( context: component.context, @@ -388,30 +530,6 @@ final class QuickReplySetupScreenComponent: Component { ) chatController.navigationPresentation = .modal self.environment?.controller()?.push(chatController) - self.messagesDisposable?.dispose() - self.messagesDisposable = (contents.messages - |> deliverOnMainQueue).startStrict(next: { [weak self] messages in - guard let self, let component = self.component else { - return - } - let messages = messages.reversed().map(EngineMessage.init) - - if messages.isEmpty { - if let index = self.items.firstIndex(where: { $0.shortcut == shortcut }) { - self.items.remove(at: index) - } - } else { - if let index = self.items.firstIndex(where: { $0.shortcut == shortcut }) { - self.items[index] = QuickReplyMessageShortcut(id: self.items[index].id, shortcut: self.items[index].shortcut, messages: messages) - } else { - self.items.insert(QuickReplyMessageShortcut(id: Int32.random(in: Int32.min ... Int32.max), shortcut: shortcut, messages: messages), at: 0) - } - } - - component.context.engine.accountData.updateShortcutMessages(state: QuickReplyMessageShortcutsState(shortcuts: self.items)) - - self.state?.updated(transition: .immediate) - }) } else { var completion: ((String?) -> Void)? let alertController = quickReplyNameAlertController( @@ -430,7 +548,12 @@ final class QuickReplySetupScreenComponent: Component { return } if let value, !value.isEmpty { - if self.items.contains(where: { $0.shortcut.lowercased() == value.lowercased() }) { + guard let shortcutMessageList = self.shortcutMessageList else { + alertController?.dismissAnimated() + return + } + + if shortcutMessageList.items.contains(where: { $0.shortcut.lowercased() == value.lowercased() }) { if let contentNode = alertController?.contentNode as? QuickReplyNameAlertContentNode { contentNode.setErrorText(errorText: "Shortcut with that name already exists") } @@ -438,7 +561,7 @@ final class QuickReplySetupScreenComponent: Component { } alertController?.dismissAnimated() - self.openQuickReplyChat(shortcut: value) + self.openQuickReplyChat(shortcut: value, shortcutId: nil) } } self.environment?.controller()?.present(alertController, in: .window(.root)) @@ -447,13 +570,11 @@ final class QuickReplySetupScreenComponent: Component { self.contentListNode?.clearHighlightAnimated(true) } - func openEditShortcut(shortcut: String) { + func openEditShortcut(id: Int32, currentValue: String) { guard let component = self.component else { return } - let currentValue = shortcut - var completion: ((String?) -> Void)? let alertController = quickReplyNameAlertController( context: component.context, @@ -475,26 +596,17 @@ final class QuickReplySetupScreenComponent: Component { alertController?.dismissAnimated() return } - - var shortcuts = self.items - guard let index = shortcuts.firstIndex(where: { $0.shortcut.lowercased() == currentValue }) else { + guard let shortcutMessageList = self.shortcutMessageList else { alertController?.dismissAnimated() return } - if shortcuts.contains(where: { $0.shortcut.lowercased() == value.lowercased() }) { + if shortcutMessageList.items.contains(where: { $0.shortcut.lowercased() == value.lowercased() }) { if let contentNode = alertController?.contentNode as? QuickReplyNameAlertContentNode { contentNode.setErrorText(errorText: "Shortcut with that name already exists") } } else { - shortcuts[index] = QuickReplyMessageShortcut( - id: shortcuts[index].id, - shortcut: value, - messages: shortcuts[index].messages - ) - self.items = shortcuts - let updatedShortcutMessages = QuickReplyMessageShortcutsState(shortcuts: shortcuts) - component.context.engine.accountData.updateShortcutMessages(state: updatedShortcutMessages) + component.context.engine.accountData.editMessageShortcut(id: id, shortcut: value) alertController?.dismissAnimated() } @@ -503,23 +615,48 @@ final class QuickReplySetupScreenComponent: Component { self.environment?.controller()?.present(alertController, in: .window(.root)) } - func openDeleteShortcut(shortcut: String) { + func toggleShortcutSelection(id: Int32) { + if self.selectedIds.contains(id) { + self.selectedIds.remove(id) + } else { + self.selectedIds.insert(id) + } + self.state?.updated(transition: .spring(duration: 0.4)) + } + + func openDeleteShortcuts(ids: [Int32]) { guard let component = self.component else { return } - var shortcuts = self.items - guard let index = shortcuts.firstIndex(where: { $0.shortcut.lowercased() == shortcut }) else { - return - } + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let actionSheet = ActionSheetController(presentationData: presentationData) + var items: [ActionSheetItem] = [] - shortcuts.remove(at: index) - self.items = shortcuts - - self.state?.updated(transition: .spring(duration: 0.4)) - - let updatedShortcutMessages = QuickReplyMessageShortcutsState(shortcuts: shortcuts) - component.context.engine.accountData.updateShortcutMessages(state: updatedShortcutMessages) + //TODO:localize + items.append(ActionSheetButtonItem(title: ids.count == 1 ? "Delete Shortcut" : "Delete Shortcuts", color: .destructive, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + guard let self, let component = self.component else { + return + } + + for id in ids { + self.selectedIds.remove(id) + } + self.contentListNode?.setPendingRemoveItems(itemIds: ids) + component.context.engine.accountData.deleteMessageShortcuts(ids: ids) + self.state?.updated(transition: .spring(duration: 0.4)) + })) + + actionSheet.setItemGroups([ + ActionSheetItemGroup(items: items), + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ]) + ]) + self.environment?.controller()?.present(actionSheet, in: .window(.root)) } private func updateNavigationBar( @@ -533,31 +670,43 @@ final class QuickReplySetupScreenComponent: Component { deferScrollApplication: Bool ) -> CGFloat { var rightButtons: [AnyComponentWithIdentity] = [] - if self.isEditing { - rightButtons.append(AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( - content: .text(title: strings.Common_Done, isBold: true), - pressed: { [weak self] _ in - guard let self else { - return + if let shortcutMessageList = self.shortcutMessageList, !shortcutMessageList.items.isEmpty { + if self.isEditing { + rightButtons.append(AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( + content: .text(title: strings.Common_Done, isBold: true), + pressed: { [weak self] _ in + guard let self else { + return + } + self.isEditing = false + self.selectedIds.removeAll() + self.state?.updated(transition: .spring(duration: 0.4)) } - self.isEditing = false - self.state?.updated(transition: .spring(duration: 0.4)) - } - )))) - } else { - rightButtons.append(AnyComponentWithIdentity(id: "edit", component: AnyComponent(NavigationButtonComponent( - content: .text(title: strings.Common_Edit, isBold: false), - pressed: { [weak self] _ in - guard let self else { - return + )))) + } else { + rightButtons.append(AnyComponentWithIdentity(id: "edit", component: AnyComponent(NavigationButtonComponent( + content: .text(title: strings.Common_Edit, isBold: false), + pressed: { [weak self] _ in + guard let self else { + return + } + self.isEditing = true + self.state?.updated(transition: .spring(duration: 0.4)) } - self.isEditing = true - self.state?.updated(transition: .spring(duration: 0.4)) - } - )))) + )))) + } } + + let titleText: String + if !self.selectedIds.isEmpty { + //TODO:localize + titleText = "\(self.selectedIds.count) Selected" + } else { + titleText = "Quick Replies" + } + let headerContent: ChatListHeaderComponent.Content? = ChatListHeaderComponent.Content( - title: "Quick Replies", + title: titleText, navigationBackTitle: nil, titleComponent: nil, chatListTitle: nil, @@ -629,9 +778,7 @@ final class QuickReplySetupScreenComponent: Component { private func updateNavigationScrolling(navigationHeight: CGFloat, transition: Transition) { var mainOffset: CGFloat - if self.items.isEmpty { - mainOffset = navigationHeight - } else { + if let shortcutMessageList = self.shortcutMessageList, !shortcutMessageList.items.isEmpty { if let contentListNode = self.contentListNode { switch contentListNode.visibleContentOffset() { case .none: @@ -644,6 +791,8 @@ final class QuickReplySetupScreenComponent: Component { } else { mainOffset = navigationHeight } + } else { + mainOffset = navigationHeight } mainOffset = min(mainOffset, ChatListNavigationBar.searchScrollHeight) @@ -674,18 +823,20 @@ final class QuickReplySetupScreenComponent: Component { if self.component == nil { self.accountPeer = component.initialData.accountPeer - self.items = component.initialData.shortcutMessages.shortcuts + self.shortcutMessageList = component.initialData.shortcutMessageList - self.itemsDisposable = (component.context.engine.accountData.shortcutMessages() - |> deliverOnMainQueue).start(next: { [weak self] shortcutMessages in + self.shortcutMessageListDisposable = (component.context.engine.accountData.shortcutMessageList() + |> deliverOnMainQueue).startStrict(next: { [weak self] shortcutMessageList in guard let self else { return } - self.items = shortcutMessages.shortcuts + self.shortcutMessageList = shortcutMessageList if !self.isUpdating { self.state?.updated(transition: .immediate) } }) + + self.keepUpdatedDisposable = component.context.engine.accountData.keepShortcutMessageListUpdated().startStrict() } let environment = environment[EnvironmentType.self].value @@ -702,7 +853,12 @@ final class QuickReplySetupScreenComponent: Component { self.backgroundColor = environment.theme.list.plainBackgroundColor } - if self.items.isEmpty { + if let shortcutMessageList = self.shortcutMessageList, !shortcutMessageList.items.isEmpty { + if let emptyState = self.emptyState { + self.emptyState = nil + emptyState.view?.removeFromSuperview() + } + } else { let emptyState: ComponentView var emptyStateTransition = transition if let current = self.emptyState { @@ -713,18 +869,18 @@ final class QuickReplySetupScreenComponent: Component { emptyStateTransition = emptyStateTransition.withAnimation(.none) } - let emptyStateFrame = CGRect(origin: CGPoint(x: 0.0, y: environment.navigationHeight), size: CGSize(width: availableSize.width, height: availableSize.height - environment.navigationHeight)) + let emptyStateFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height)) let _ = emptyState.update( transition: emptyStateTransition, component: AnyComponent(QuickReplyEmptyStateComponent( theme: environment.theme, strings: environment.strings, - insets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: environment.safeInsets.bottom, right: environment.safeInsets.right), + insets: UIEdgeInsets(top: environment.navigationHeight, left: environment.safeInsets.left, bottom: environment.safeInsets.bottom, right: environment.safeInsets.right), action: { [weak self] in guard let self else { return } - self.openQuickReplyChat(shortcut: nil) + self.openQuickReplyChat(shortcut: nil, shortcutId: nil) } )), environment: {}, @@ -736,13 +892,9 @@ final class QuickReplySetupScreenComponent: Component { } emptyStateTransition.setFrame(view: emptyStateView, frame: emptyStateFrame) } - } else { - if let emptyState = self.emptyState { - self.emptyState = nil - emptyState.view?.removeFromSuperview() - } } + var listBottomInset = environment.safeInsets.bottom let navigationHeight = self.updateNavigationBar( component: component, theme: environment.theme, @@ -755,6 +907,78 @@ final class QuickReplySetupScreenComponent: Component { ) self.navigationHeight = navigationHeight + if !self.selectedIds.isEmpty { + let selectionPanel: ComponentView + var selectionPanelTransition = transition + if let current = self.selectionPanel { + selectionPanel = current + } else { + selectionPanelTransition = selectionPanelTransition.withAnimation(.none) + selectionPanel = ComponentView() + self.selectionPanel = selectionPanel + } + + let buttonTitle: String + if self.selectedIds.count == 1 { + buttonTitle = "Delete 1 Quick Reply" + } else { + buttonTitle = "Delete \(self.selectedIds.count) Quick Replies" + } + + let selectionPanelSize = selectionPanel.update( + transition: selectionPanelTransition, + component: AnyComponent(BottomPanelComponent( + theme: environment.theme, + content: AnyComponentWithIdentity(id: 0, component: AnyComponent(PlainButtonComponent( + content: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: buttonTitle, font: Font.regular(17.0), textColor: environment.theme.list.itemDestructiveColor)) + )), + background: nil, + effectAlignment: .center, + minSize: CGSize(width: availableSize.width - environment.safeInsets.left - environment.safeInsets.right, height: 44.0), + contentInsets: UIEdgeInsets(), + action: { [weak self] in + guard let self else { + return + } + if self.selectedIds.isEmpty { + return + } + self.openDeleteShortcuts(ids: Array(self.selectedIds)) + }, + animateAlpha: true, + animateScale: false, + animateContents: false + ))), + insets: UIEdgeInsets(top: 4.0, left: environment.safeInsets.left, bottom: environment.safeInsets.bottom, right: environment.safeInsets.right) + )), + environment: {}, + containerSize: availableSize + ) + let selectionPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - selectionPanelSize.height), size: selectionPanelSize) + listBottomInset = selectionPanelSize.height + if let selectionPanelView = selectionPanel.view { + var animateIn = false + if selectionPanelView.superview == nil { + animateIn = true + self.addSubview(selectionPanelView) + } + selectionPanelTransition.setFrame(view: selectionPanelView, frame: selectionPanelFrame) + if animateIn { + transition.animatePosition(view: selectionPanelView, from: CGPoint(x: 0.0, y: selectionPanelFrame.height), to: CGPoint(), additive: true) + } + } + } else { + if let selectionPanel = self.selectionPanel { + self.selectionPanel = nil + if let selectionPanelView = selectionPanel.view { + transition.setPosition(view: selectionPanelView, position: CGPoint(x: selectionPanelView.center.x, y: availableSize.height + selectionPanelView.bounds.height * 0.5), completion: { [weak selectionPanelView] _ in + selectionPanelView?.removeFromSuperview() + }) + } + } + } + let contentListNode: ContentListNode if let current = self.contentListNode { contentListNode = current @@ -773,7 +997,9 @@ final class QuickReplySetupScreenComponent: Component { self.updateNavigationScrolling(navigationHeight: navigationHeight, transition: .immediate) } - if let navigationBarComponentView = self.navigationBarView.view { + if let selectionPanelView = self.selectionPanel?.view { + self.insertSubview(contentListNode.view, belowSubview: selectionPanelView) + } else if let navigationBarComponentView = self.navigationBarView.view { self.insertSubview(contentListNode.view, belowSubview: navigationBarComponentView) } else { self.addSubview(contentListNode.view) @@ -781,18 +1007,22 @@ final class QuickReplySetupScreenComponent: Component { } transition.setFrame(view: contentListNode.view, frame: CGRect(origin: CGPoint(), size: availableSize)) - contentListNode.update(size: availableSize, insets: UIEdgeInsets(top: navigationHeight, left: environment.safeInsets.left, bottom: environment.safeInsets.bottom, right: environment.safeInsets.right), transition: transition) + contentListNode.update(size: availableSize, insets: UIEdgeInsets(top: navigationHeight, left: environment.safeInsets.left, bottom: listBottomInset, right: environment.safeInsets.right), transition: transition) var entries: [ContentEntry] = [] - if let accountPeer = self.accountPeer { + if let shortcutMessageList = self.shortcutMessageList, let accountPeer = self.accountPeer { entries.append(.add) - for item in self.items { - entries.append(.item(item: item, accountPeer: accountPeer, sortIndex: entries.count, isEditing: self.isEditing)) + for item in shortcutMessageList.items { + entries.append(.item(item: item, accountPeer: accountPeer, sortIndex: entries.count, isEditing: self.isEditing, isSelected: self.selectedIds.contains(item.id))) } } contentListNode.setEntries(entries: entries, animated: !transition.animation.isImmediate) - contentListNode.isHidden = self.items.isEmpty + if let shortcutMessageList = self.shortcutMessageList, !shortcutMessageList.items.isEmpty { + contentListNode.isHidden = false + } else { + contentListNode.isHidden = true + } self.updateNavigationScrolling(navigationHeight: navigationHeight, transition: transition) @@ -817,14 +1047,14 @@ final class QuickReplySetupScreenComponent: Component { public final class QuickReplySetupScreen: ViewControllerComponentContainer { public final class InitialData: QuickReplySetupScreenInitialData { let accountPeer: EnginePeer? - let shortcutMessages: QuickReplyMessageShortcutsState + let shortcutMessageList: ShortcutMessageList init( accountPeer: EnginePeer?, - shortcutMessages: QuickReplyMessageShortcutsState + shortcutMessageList: ShortcutMessageList ) { self.accountPeer = accountPeer - self.shortcutMessages = shortcutMessages + self.shortcutMessageList = shortcutMessageList } } @@ -874,13 +1104,13 @@ public final class QuickReplySetupScreen: ViewControllerComponentContainer { context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId) ), - context.engine.accountData.shortcutMessages() + context.engine.accountData.shortcutMessageList() |> take(1) ) - |> map { accountPeer, shortcutMessages -> QuickReplySetupScreenInitialData in + |> map { accountPeer, shortcutMessageList -> QuickReplySetupScreenInitialData in return InitialData( accountPeer: accountPeer, - shortcutMessages: shortcutMessages + shortcutMessageList: shortcutMessageList ) } } diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Empty Chat/QuickReplies.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Empty Chat/QuickReplies.imageset/Contents.json new file mode 100644 index 0000000000..5146f4aea0 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Empty Chat/QuickReplies.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "quickrepliesdemo.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Empty Chat/QuickReplies.imageset/quickrepliesdemo.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Empty Chat/QuickReplies.imageset/quickrepliesdemo.pdf new file mode 100644 index 0000000000..7dd7ee40f2 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Empty Chat/QuickReplies.imageset/quickrepliesdemo.pdf @@ -0,0 +1,127 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 10.000000 7.000000 cm +0.000000 0.000000 0.000000 scn +25.000000 65.066650 m +11.192883 65.066650 0.000000 54.891304 0.000000 42.339378 c +0.000000 29.787453 11.192883 19.612106 25.000000 19.612106 c +27.116848 19.612106 29.172245 19.851284 31.135277 20.301537 c +31.506607 20.386707 31.928230 19.997253 32.632072 19.347115 c +33.483276 18.560856 34.747246 17.393326 36.834217 16.222927 c +39.500584 14.727596 43.194855 14.857323 43.820072 15.119576 c +44.419498 15.371006 43.958118 15.870796 43.145599 16.750961 c +42.583481 17.359879 41.853298 18.150848 41.190041 19.167587 c +39.568069 21.653973 40.238289 24.614052 40.965954 25.146793 c +46.656067 29.312656 50.000000 35.180622 50.000000 42.339378 c +50.000000 54.891304 38.807121 65.066650 25.000000 65.066650 c +h +50.000000 16.666668 m +50.000000 21.333771 50.000000 23.667324 50.908279 25.449921 c +51.707222 27.017937 52.982063 28.292778 54.550079 29.091721 c +56.332676 30.000000 58.666229 30.000000 63.333332 30.000000 c +66.666672 30.000000 l +71.333771 30.000000 73.667328 30.000000 75.449921 29.091721 c +77.017937 28.292778 78.292778 27.017937 79.091721 25.449921 c +80.000000 23.667324 80.000000 21.333771 80.000000 16.666668 c +80.000000 13.333332 l +80.000000 8.666229 80.000000 6.332676 79.091721 4.550079 c +78.292778 2.982063 77.017937 1.707222 75.449921 0.908279 c +73.667328 0.000000 71.333771 0.000000 66.666664 0.000000 c +63.333332 0.000000 l +58.666229 0.000000 56.332676 0.000000 54.550079 0.908279 c +52.982063 1.707222 51.707222 2.982063 50.908279 4.550079 c +50.000000 6.332676 50.000000 8.666229 50.000000 13.333336 c +50.000000 16.666668 l +h +55.723064 6.619629 m +55.723064 5.921387 56.220295 5.381836 57.098389 5.381836 c +57.849529 5.381836 58.283287 5.730957 58.505455 6.492676 c +63.192139 22.668621 l +63.245037 22.848469 63.266197 23.007160 63.266197 23.197590 c +63.266197 23.969891 62.716064 24.498859 61.890869 24.498859 c +61.139732 24.498859 60.705975 24.118000 60.473228 23.303387 c +55.797119 7.180340 l +55.754803 7.021648 55.723064 6.831219 55.723064 6.619629 c +h +62.353432 7.801270 m +62.353432 6.880859 62.977619 6.298992 63.972084 6.298992 c +64.818443 6.298992 65.294518 6.722168 65.590736 7.737793 c +66.394775 10.118164 l +71.906662 10.118164 l +72.710693 7.706055 l +72.996338 6.711590 73.472412 6.298992 74.371658 6.298992 c +75.302658 6.298992 75.969162 6.923176 75.969162 7.801270 c +75.969162 8.118652 75.916260 8.404297 75.768143 8.816895 c +71.578690 20.179199 l +71.123779 21.427570 70.383217 21.977699 69.134842 21.977699 c +67.928787 21.977699 67.177650 21.406410 66.733315 20.168617 c +62.565022 8.816895 l +62.427490 8.425457 62.353432 8.065754 62.353432 7.801270 c +h +71.166092 12.593750 m +67.093018 12.593750 l +69.071365 18.846191 l +69.145424 18.846191 l +71.166092 12.593750 l +h +f* +n +Q + +endstream +endobj + +3 0 obj + 2818 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 100.000000 80.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002908 00000 n +0000002931 00000 n +0000003105 00000 n +0000003179 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +3238 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Resources/Animations/WriteEmoji.tgs b/submodules/TelegramUI/Resources/Animations/WriteEmoji.tgs new file mode 100644 index 0000000000..47caac05a2 Binary files /dev/null and b/submodules/TelegramUI/Resources/Animations/WriteEmoji.tgs differ diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift index 7ce0929b1e..ef430bc3d9 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift @@ -139,7 +139,7 @@ extension ChatControllerImpl { completion?() }) - } else if case let .peer(peerId) = self.chatLocation, let messageId = messageLocation.messageId, (messageId.peerId != peerId && !forceInCurrentChat) || (isScheduledMessages && messageId.id != 0 && !Namespaces.Message.allScheduled.contains(messageId.namespace)) { + } else if case let .peer(peerId) = self.chatLocation, let messageId = messageLocation.messageId, (messageId.peerId != peerId && !forceInCurrentChat) || (isScheduledMessages && messageId.id != 0 && !Namespaces.Message.allNonRegular.contains(messageId.namespace)) { let _ = (self.context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId), TelegramEngine.EngineData.Item.Messages.Message(id: messageId) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index d26e815e00..366c8831fd 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -6180,7 +6180,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case .awayMessageInput: self.chatTitleView?.titleContent = .custom("Away Message", nil, false) case let .quickReplyMessageInput(shortcut): - self.chatTitleView?.titleContent = .custom("/\(shortcut)", nil, false) + self.chatTitleView?.titleContent = .custom("\(shortcut)", nil, false) } } else { self.chatTitleView?.titleContent = .custom("Messages", nil, false) @@ -9105,15 +9105,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let sourceMessage: Signal - if case let .customChatContents(customChatContents) = strongSelf.subject { - sourceMessage = customChatContents.messages - |> take(1) - |> map { messages -> EngineMessage? in - return messages.first(where: { $0.id == editMessage.messageId }).flatMap(EngineMessage.init) - } - } else { - sourceMessage = strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: editMessage.messageId)) - } + sourceMessage = strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: editMessage.messageId)) let _ = (sourceMessage |> deliverOnMainQueue).start(next: { [weak strongSelf] message in @@ -9210,37 +9202,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G media = .keep } - if case let .customChatContents(customChatContents) = strongSelf.subject { - customChatContents.editMessage(id: editMessage.messageId, text: text.string, media: media, entities: entitiesAttribute, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview) - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in - var state = state - state = state.updatedInterfaceState({ $0.withUpdatedEditMessage(nil) }) - state = state.updatedEditMessageState(nil) - return state - }) - } else { - let _ = (strongSelf.context.account.postbox.messageAtId(editMessage.messageId) - |> deliverOnMainQueue).startStandalone(next: { [weak self] currentMessage in - if let strongSelf = self { - if let currentMessage = currentMessage { - let currentEntities = currentMessage.textEntitiesAttribute?.entities ?? [] - let currentWebpagePreviewAttribute = currentMessage.webpagePreviewAttribute ?? WebpagePreviewMessageAttribute(leadingPreview: false, forceLargeMedia: nil, isManuallyAdded: true, isSafe: false) - - if currentMessage.text != text.string || currentEntities != entities || updatingMedia || webpagePreviewAttribute != currentWebpagePreviewAttribute || disableUrlPreview { - strongSelf.context.account.pendingUpdateMessageManager.add(messageId: editMessage.messageId, text: text.string, media: media, entities: entitiesAttribute, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview) - } - } + let _ = (strongSelf.context.account.postbox.messageAtId(editMessage.messageId) + |> deliverOnMainQueue).startStandalone(next: { [weak self] currentMessage in + if let strongSelf = self { + if let currentMessage = currentMessage { + let currentEntities = currentMessage.textEntitiesAttribute?.entities ?? [] + let currentWebpagePreviewAttribute = currentMessage.webpagePreviewAttribute ?? WebpagePreviewMessageAttribute(leadingPreview: false, forceLargeMedia: nil, isManuallyAdded: true, isSafe: false) - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in - var state = state - state = state.updatedInterfaceState({ $0.withUpdatedEditMessage(nil) }) - state = state.updatedEditMessageState(nil) - return state - }) + if currentMessage.text != text.string || currentEntities != entities || updatingMedia || webpagePreviewAttribute != currentWebpagePreviewAttribute || disableUrlPreview { + strongSelf.context.account.pendingUpdateMessageManager.add(messageId: editMessage.messageId, text: text.string, media: media, entities: entitiesAttribute, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview) + } } - }) - } + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in + var state = state + state = state.updatedInterfaceState({ $0.withUpdatedEditMessage(nil) }) + state = state.updatedEditMessageState(nil) + return state + }) + } + }) }) }, beginMessageSearch: { [weak self] domain, query in guard let strongSelf = self else { @@ -9479,12 +9460,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } } - }, sendShortcut: { [weak self] shortcut in + }, sendShortcut: { [weak self] shortcutId in guard let self else { return } + guard let peerId = self.chatLocation.peerId else { + return + } + let _ = self + let _ = shortcutId - self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in + self.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreviews([]) } + }) + self.context.engine.accountData.sendMessageShortcut(peerId: peerId, id: shortcutId) + + /*self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in guard let self else { return } @@ -9494,7 +9485,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, nil) var messages: [EnqueueMessage] = [] - for message in shortcut.messages { + do { + let message = shortcut.topMessage var attributes: [MessageAttribute] = [] let entities = generateTextEntities(message.text, enabledTypes: .all) if !entities.isEmpty { @@ -9515,7 +9507,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G )) } - self.sendMessages(messages) + self.sendMessages(messages)*/ }, openEditShortcuts: { [weak self] in guard let self else { return diff --git a/submodules/TelegramUI/Sources/ChatControllerEditChat.swift b/submodules/TelegramUI/Sources/ChatControllerEditChat.swift index 59f7290162..a7c7370f72 100644 --- a/submodules/TelegramUI/Sources/ChatControllerEditChat.swift +++ b/submodules/TelegramUI/Sources/ChatControllerEditChat.swift @@ -11,6 +11,7 @@ import QuickReplyNameAlertController extension ChatControllerImpl { func editChat() { + //TODO:localize if case let .customChatContents(customChatContents) = self.subject, case let .quickReplyMessageInput(currentValue) = customChatContents.kind { var completion: ((String?) -> Void)? let alertController = quickReplyNameAlertController( @@ -34,40 +35,25 @@ extension ChatControllerImpl { return } - let _ = (self.context.engine.accountData.shortcutMessages() + let _ = (self.context.engine.accountData.shortcutMessageList() |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] shortcutMessages in + |> deliverOnMainQueue).start(next: { [weak self] shortcutMessageList in guard let self else { alertController?.dismissAnimated() return } - var shortcuts = shortcutMessages.shortcuts - guard let index = shortcuts.firstIndex(where: { $0.shortcut.lowercased() == currentValue }) else { - alertController?.dismissAnimated() - return - } - - if shortcuts.contains(where: { $0.shortcut.lowercased() == value.lowercased() }) { + if shortcutMessageList.items.contains(where: { $0.shortcut.lowercased() == value.lowercased() }) { if let contentNode = alertController?.contentNode as? QuickReplyNameAlertContentNode { contentNode.setErrorText(errorText: "Shortcut with that name already exists") } } else { - shortcuts[index] = QuickReplyMessageShortcut( - id: shortcuts[index].id, - shortcut: value, - messages: shortcuts[index].messages - ) - let updatedShortcutMessages = QuickReplyMessageShortcutsState(shortcuts: shortcuts) - self.context.engine.accountData.updateShortcutMessages(state: updatedShortcutMessages) - - self.chatTitleView?.titleContent = .custom("/\(value)", nil, false) + self.chatTitleView?.titleContent = .custom("\(value)", nil, false) + alertController?.dismissAnimated() if case let .customChatContents(customChatContents) = self.subject { customChatContents.quickReplyUpdateShortcut(value: value) } - - alertController?.dismissAnimated() } }) } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index faa7465e86..50e8551bb6 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -590,15 +590,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } } else if case .customChatContents = chatLocation { if case let .customChatContents(customChatContents) = subject { - source = .custom( - messages: customChatContents.messages - |> map { messages in - return (messages, 0, false) - }, - messageId: nil, - quote: nil, - loadMore: nil - ) + source = .customView(historyView: customChatContents.historyView) } else { source = .custom(messages: .single(([], 0, false)), messageId: nil, quote: nil, loadMore: nil) } diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenMessageShareMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenMessageShareMenu.swift index 1e6576268b..d1445389c0 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenMessageShareMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenMessageShareMenu.swift @@ -31,7 +31,7 @@ func chatShareToSavedMessagesAdditionalView(_ chatController: ChatControllerImpl return } - let _ = (chatController.context.account.postbox.aroundMessageHistoryViewForLocation(.peer(peerId: chatController.context.account.peerId, threadId: nil), anchor: .upperBound, ignoreMessagesInTimestampRange: nil, count: 45, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: []) + let _ = (chatController.context.account.postbox.aroundMessageHistoryViewForLocation(.peer(peerId: chatController.context.account.peerId, threadId: nil), anchor: .upperBound, ignoreMessagesInTimestampRange: nil, count: 45, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .not(Namespaces.Message.allNonRegular), orderStatistics: []) |> map { view, _, _ -> [EngineMessage.Id] in let messageIds = correlationIds.compactMap { correlationId in return chatController.context.engine.messages.synchronouslyLookupCorrelationId(correlationId: correlationId) diff --git a/submodules/TelegramUI/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Sources/ChatEmptyNode.swift index 013b467cba..6b98a57487 100644 --- a/submodules/TelegramUI/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Sources/ChatEmptyNode.swift @@ -703,8 +703,22 @@ private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeC func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { var maxWidth: CGFloat = size.width var centerText = false - if case .customChatContents = interfaceState.subject { + + var insets = UIEdgeInsets(top: 15.0, left: 15.0, bottom: 15.0, right: 15.0) + var imageSpacing: CGFloat = 12.0 + var titleSpacing: CGFloat = 4.0 + + if case let .customChatContents(customChatContents) = interfaceState.subject { maxWidth = min(240.0, maxWidth) + + switch customChatContents.kind { + case .greetingMessageInput, .awayMessageInput: + break + case .quickReplyMessageInput: + insets.top = 10.0 + imageSpacing = 5.0 + titleSpacing = 5.0 + } } if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { @@ -713,7 +727,7 @@ private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeC let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper) - self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Empty Chat/Cloud"), color: serviceColor.primaryText) + var iconName = "Chat/Empty Chat/Cloud" let titleString: String let strings: [String] @@ -735,6 +749,7 @@ private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeC "Add messages that are automatically sent when you are off." ] case let .quickReplyMessageInput(shortcut): + iconName = "Chat/Empty Chat/QuickReplies" //TODO:localize centerText = false titleString = "New Quick Reply" @@ -753,6 +768,8 @@ private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeC ] } + self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: iconName), color: serviceColor.primaryText) + self.titleNode.attributedText = NSAttributedString(string: titleString, font: titleFont, textColor: serviceColor.primaryText) let lines: [NSAttributedString] = strings.map { @@ -781,11 +798,6 @@ private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeC } } - let insets = UIEdgeInsets(top: 15.0, left: 15.0, bottom: 15.0, right: 15.0) - - let imageSpacing: CGFloat = 12.0 - let titleSpacing: CGFloat = 4.0 - var contentWidth: CGFloat = 100.0 var contentHeight: CGFloat = 0.0 diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 3b7ddd21f0..bbd4200418 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -794,35 +794,35 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto //self.debugInfo = true self.messageProcessingManager.process = { [weak context] messageIds in - context?.account.viewTracker.updateViewCountForMessageIds(messageIds: messageIds, clientId: clientId.with { $0 }) + context?.account.viewTracker.updateViewCountForMessageIds(messageIds: Set(messageIds.map(\.messageId)), clientId: clientId.with { $0 }) } self.messageWithReactionsProcessingManager.process = { [weak context] messageIds in - context?.account.viewTracker.updateReactionsForMessageIds(messageIds: messageIds) + context?.account.viewTracker.updateReactionsForMessageIds(messageIds: Set(messageIds.map(\.messageId))) } self.seenLiveLocationProcessingManager.process = { [weak context] messageIds in - context?.account.viewTracker.updateSeenLiveLocationForMessageIds(messageIds: messageIds) + context?.account.viewTracker.updateSeenLiveLocationForMessageIds(messageIds: Set(messageIds.map(\.messageId))) } self.unsupportedMessageProcessingManager.process = { [weak context] messageIds in context?.account.viewTracker.updateUnsupportedMediaForMessageIds(messageIds: messageIds) } self.refreshMediaProcessingManager.process = { [weak context] messageIds in - context?.account.viewTracker.refreshSecretMediaMediaForMessageIds(messageIds: messageIds) + context?.account.viewTracker.refreshSecretMediaMediaForMessageIds(messageIds: Set(messageIds.map(\.messageId))) } self.refreshStoriesProcessingManager.process = { [weak context] messageIds in - context?.account.viewTracker.refreshStoriesForMessageIds(messageIds: messageIds) + context?.account.viewTracker.refreshStoriesForMessageIds(messageIds: Set(messageIds.map(\.messageId))) } self.translationProcessingManager.process = { [weak self, weak context] messageIds in if let context = context, let toLang = self?.toLang { - let _ = translateMessageIds(context: context, messageIds: Array(messageIds), toLang: toLang).startStandalone() + let _ = translateMessageIds(context: context, messageIds: Array(messageIds.map(\.messageId)), toLang: toLang).startStandalone() } } self.messageMentionProcessingManager.process = { [weak self, weak context] messageIds in if let strongSelf = self { if strongSelf.canReadHistoryValue { - context?.account.viewTracker.updateMarkMentionsSeenForMessageIds(messageIds: messageIds) + context?.account.viewTracker.updateMarkMentionsSeenForMessageIds(messageIds: Set(messageIds.map(\.messageId))) } else { - strongSelf.messageIdsScheduledForMarkAsSeen.formUnion(messageIds) + strongSelf.messageIdsScheduledForMarkAsSeen.formUnion(messageIds.map(\.messageId)) } } } @@ -832,9 +832,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto return } if strongSelf.canReadHistoryValue && !strongSelf.suspendReadingReactions && !strongSelf.context.sharedContext.immediateExperimentalUISettings.skipReadHistory { - strongSelf.context.account.viewTracker.updateMarkReactionsSeenForMessageIds(messageIds: messageIds) + strongSelf.context.account.viewTracker.updateMarkReactionsSeenForMessageIds(messageIds: Set(messageIds.map(\.messageId))) } else { - strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.formUnion(messageIds) + strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.formUnion(messageIds.map(\.messageId)) } } @@ -842,7 +842,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto guard let strongSelf = self else { return } - strongSelf.context.account.viewTracker.updatedExtendedMediaForMessageIds(messageIds: messageIds) + strongSelf.context.account.viewTracker.updatedExtendedMediaForMessageIds(messageIds: Set(messageIds.map(\.messageId))) } self.preloadPages = false @@ -1269,6 +1269,53 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto return (ChatHistoryViewUpdate.HistoryView(view: MessageHistoryView(tag: nil, namespaces: .all, entries: messages.reversed().map { MessageHistoryEntry(message: $0, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)) }, holeEarlier: hasMore, holeLater: false, isLoading: false), type: .Generic(type: version > 0 ? ViewUpdateType.Generic : ViewUpdateType.Initial), scrollPosition: scrollPosition, flashIndicators: false, originalScrollPosition: nil, initialData: ChatHistoryCombinedInitialData(initialData: nil, buttonKeyboardMessage: nil, cachedData: nil, cachedDataMessages: nil, readStateData: nil), id: 0), version, nil, nil) } + } else if case let .customView(historyView) = self.source { + historyViewUpdate = combineLatest(queue: .mainQueue(), + self.chatHistoryLocationPromise.get(), + self.ignoreMessagesInTimestampRangePromise.get() + ) + |> distinctUntilChanged(isEqual: { lhs, rhs in + if lhs.0 != rhs.0 { + return false + } + if lhs.1 != rhs.1 { + return false + } + return true + }) + |> mapToSignal { _ in + return historyView + } + |> map { view, update in + let version = currentViewVersion.modify({ value in + if let value = value { + return value + 1 + } else { + return 0 + } + })! + + return ( + ChatHistoryViewUpdate.HistoryView( + view: view, + type: .Generic(type: update), + scrollPosition: nil, + flashIndicators: false, + originalScrollPosition: nil, + initialData: ChatHistoryCombinedInitialData( + initialData: nil, + buttonKeyboardMessage: nil, + cachedData: nil, + cachedDataMessages: nil, + readStateData: nil + ), + id: 0 + ), + version, + nil, + nil + ) + } } else { historyViewUpdate = combineLatest(queue: .mainQueue(), self.chatHistoryLocationPromise.get(), @@ -2415,7 +2462,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto var messageIdsWithViewCount: [MessageId] = [] var messageIdsWithLiveLocation: [MessageId] = [] - var messageIdsWithUnsupportedMedia: [MessageId] = [] + var messageIdsWithUnsupportedMedia: [MessageAndThreadId] = [] var messageIdsWithRefreshMedia: [MessageId] = [] var messageIdsWithRefreshStories: [MessageId] = [] var messageIdsWithUnseenPersonalMention: [MessageId] = [] @@ -2516,7 +2563,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } } if contentRequiredValidation { - messageIdsWithUnsupportedMedia.append(message.id) + messageIdsWithUnsupportedMedia.append(MessageAndThreadId(messageId: message.id, threadId: message.threadId)) } if mediaRequiredValidation { messageIdsWithRefreshMedia.append(message.id) @@ -2712,41 +2759,41 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } if !messageIdsWithViewCount.isEmpty { - self.messageProcessingManager.add(messageIdsWithViewCount) + self.messageProcessingManager.add(messageIdsWithViewCount.map { MessageAndThreadId(messageId: $0, threadId: nil) }) } if !messageIdsWithLiveLocation.isEmpty { - self.seenLiveLocationProcessingManager.add(messageIdsWithLiveLocation) + self.seenLiveLocationProcessingManager.add(messageIdsWithLiveLocation.map { MessageAndThreadId(messageId: $0, threadId: nil) }) } if !messageIdsWithUnsupportedMedia.isEmpty { self.unsupportedMessageProcessingManager.add(messageIdsWithUnsupportedMedia) } if !messageIdsWithRefreshMedia.isEmpty { - self.refreshMediaProcessingManager.add(messageIdsWithRefreshMedia) + self.refreshMediaProcessingManager.add(messageIdsWithRefreshMedia.map { MessageAndThreadId(messageId: $0, threadId: nil) }) } if !messageIdsWithRefreshStories.isEmpty { - self.refreshStoriesProcessingManager.add(messageIdsWithRefreshStories) + self.refreshStoriesProcessingManager.add(messageIdsWithRefreshStories.map { MessageAndThreadId(messageId: $0, threadId: nil) }) } if !messageIdsWithUnseenPersonalMention.isEmpty { - self.messageMentionProcessingManager.add(messageIdsWithUnseenPersonalMention) + self.messageMentionProcessingManager.add(messageIdsWithUnseenPersonalMention.map { MessageAndThreadId(messageId: $0, threadId: nil) }) } if !messageIdsWithUnseenReactions.isEmpty { - self.unseenReactionsProcessingManager.add(messageIdsWithUnseenReactions) + self.unseenReactionsProcessingManager.add(messageIdsWithUnseenReactions.map { MessageAndThreadId(messageId: $0, threadId: nil) }) if self.canReadHistoryValue && !self.context.sharedContext.immediateExperimentalUISettings.skipReadHistory { let _ = self.displayUnseenReactionAnimations(messageIds: messageIdsWithUnseenReactions) } } if !messageIdsWithPossibleReactions.isEmpty { - self.messageWithReactionsProcessingManager.add(messageIdsWithPossibleReactions) + self.messageWithReactionsProcessingManager.add(messageIdsWithPossibleReactions.map { MessageAndThreadId(messageId: $0, threadId: nil) }) } if !downloadableResourceIds.isEmpty { let _ = markRecentDownloadItemsAsSeen(postbox: self.context.account.postbox, items: downloadableResourceIds).startStandalone() } if !messageIdsWithInactiveExtendedMedia.isEmpty { - self.extendedMediaProcessingManager.update(messageIdsWithInactiveExtendedMedia) + self.extendedMediaProcessingManager.update(Set(messageIdsWithInactiveExtendedMedia.map { MessageAndThreadId(messageId: $0, threadId: nil) })) } if !messageIdsToTranslate.isEmpty { - self.translationProcessingManager.add(messageIdsToTranslate) + self.translationProcessingManager.add(messageIdsToTranslate.map { MessageAndThreadId(messageId: $0, threadId: nil) }) } if !visibleAdOpaqueIds.isEmpty { for opaqueId in visibleAdOpaqueIds { @@ -3650,7 +3697,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto let visibleNewIncomingReactionMessageIds = strongSelf.displayUnseenReactionAnimations(messageIds: messageIds) if !visibleNewIncomingReactionMessageIds.isEmpty { - strongSelf.unseenReactionsProcessingManager.add(visibleNewIncomingReactionMessageIds) + strongSelf.unseenReactionsProcessingManager.add(visibleNewIncomingReactionMessageIds.map { MessageAndThreadId(messageId: $0, threadId: nil) }) } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 0d41d323aa..4f03d1f2c4 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -711,7 +711,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } } - if Namespaces.Message.allScheduled.contains(message.id.namespace) || message.id.peerId.isReplies { + if Namespaces.Message.allNonRegular.contains(message.id.namespace) || message.id.peerId.isReplies { canReply = false canPin = false } else if messages[0].flags.intersection([.Failed, .Unsent]).isEmpty { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift index 002df47b9a..fb7f548a16 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift @@ -218,11 +218,11 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee )) }) } - var shortcuts: Signal<[QuickReplyMessageShortcut], NoError> = .single([]) + var shortcuts: Signal<[ShortcutMessageList.Item], NoError> = .single([]) if peer is TelegramUser { - shortcuts = context.engine.accountData.shortcutMessages() - |> map { shortcuts -> [QuickReplyMessageShortcut] in - return shortcuts.shortcuts.filter { item in + shortcuts = context.engine.accountData.shortcutMessageList() + |> map { shortcutMessageList -> [ShortcutMessageList.Item] in + return shortcutMessageList.items.filter { item in return item.shortcut.hasPrefix(normalizedQuery) } } diff --git a/submodules/TelegramUI/Sources/ChatMessageThrottledProcessingManager.swift b/submodules/TelegramUI/Sources/ChatMessageThrottledProcessingManager.swift index a7eaa0f5b2..a29c5cb6f1 100644 --- a/submodules/TelegramUI/Sources/ChatMessageThrottledProcessingManager.swift +++ b/submodules/TelegramUI/Sources/ChatMessageThrottledProcessingManager.swift @@ -4,30 +4,30 @@ import Postbox import SwiftSignalKit final class ChatMessageThrottledProcessingManager { - private let queue = Queue() + private let queue = Queue.mainQueue() private let delay: Double private let submitInterval: Double? - var process: ((Set) -> Void)? + var process: ((Set) -> Void)? private var timer: SwiftSignalKit.Timer? - private var processedList: [MessageId] = [] - private var processed: [MessageId: Double] = [:] - private var buffer = Set() + private var processedList: [MessageAndThreadId] = [] + private var processed: [MessageAndThreadId: Double] = [:] + private var buffer = Set() init(delay: Double = 1.0, submitInterval: Double? = nil) { self.delay = delay self.submitInterval = submitInterval } - func setProcess(process: @escaping (Set) -> Void) { + func setProcess(process: @escaping (Set) -> Void) { self.queue.async { self.process = process } } - func add(_ messageIds: [MessageId]) { + func add(_ messageIds: [MessageAndThreadId]) { self.queue.async { let timestamp = CFAbsoluteTimeGetCurrent() @@ -76,13 +76,13 @@ final class ChatMessageThrottledProcessingManager { final class ChatMessageVisibleThrottledProcessingManager { - private let queue = Queue() + private let queue = Queue.mainQueue() private let interval: Double - private var currentIds = Set() + private var currentIds = Set() - var process: ((Set) -> Void)? + var process: ((Set) -> Void)? private let timer: SwiftSignalKit.Timer @@ -107,13 +107,13 @@ final class ChatMessageVisibleThrottledProcessingManager { self.timer.invalidate() } - func setProcess(process: @escaping (Set) -> Void) { + func setProcess(process: @escaping (Set) -> Void) { self.queue.async { self.process = process } } - func update(_ ids: Set) { + func update(_ ids: Set) { self.queue.async { if self.currentIds != ids { self.currentIds = ids diff --git a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift index e7f2bca16b..6ecb5d7693 100644 --- a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift @@ -195,7 +195,7 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable { filterData: nil, index: EngineChatList.Item.Index.chatList(ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: context.account.peerId, namespace: 0, id: 0), timestamp: 0))), content: .peer(ChatListItemContent.PeerData( - messages: shortcut.messages.first.flatMap({ [$0] }) ?? [], + messages: [shortcut.topMessage], peer: renderedPeer, threadInfo: nil, combinedReadState: nil, @@ -375,7 +375,7 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { } } case let .shortcut(shortcut): - interfaceInteraction.sendShortcut(shortcut) + interfaceInteraction.sendShortcut(shortcut.id) } }, openEditShortcuts: { [weak self] in guard let self, let interfaceInteraction = self.interfaceInteraction else { diff --git a/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift index c6e029ff09..61f6f39189 100644 --- a/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift @@ -284,7 +284,10 @@ final class EditAccessoryPanelNode: AccessoryPanelNode { } let titleString: String - if canEditMedia { + if let message, message.id.namespace == Namespaces.Message.QuickReplyCloud { + //TODO:localize + titleString = "Edit Quick Reply" + } else if canEditMedia { titleString = isPhoto ? self.strings.Conversation_EditingPhotoPanelTitle : self.strings.Conversation_EditingCaptionPanelTitle } else { titleString = self.strings.Conversation_EditingMessagePanelTitle diff --git a/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift b/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift index f0b44c24c0..6830e72ed3 100644 --- a/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift +++ b/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift @@ -549,8 +549,10 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { let namespaces: MessageIdNamespaces if Namespaces.Message.allScheduled.contains(anchor.id.namespace) { namespaces = .just(Namespaces.Message.allScheduled) + } else if Namespaces.Message.allQuickReply.contains(anchor.id.namespace) { + namespaces = .just(Namespaces.Message.allQuickReply) } else { - namespaces = .not(Namespaces.Message.allScheduled) + namespaces = .not(Namespaces.Message.allNonRegular) } switch anchor { diff --git a/submodules/TelegramUI/Sources/PrefetchManager.swift b/submodules/TelegramUI/Sources/PrefetchManager.swift index c404dc2487..be6dafa68a 100644 --- a/submodules/TelegramUI/Sources/PrefetchManager.swift +++ b/submodules/TelegramUI/Sources/PrefetchManager.swift @@ -174,16 +174,16 @@ private final class PrefetchManagerInnerImpl { if case .full = automaticDownload { if let image = media as? TelegramMediaImage { - context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource._asResource(), userInitiated: false, priority: priority, storeToDownloadsPeerId: nil).startStrict()) + context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false, threadId: nil), image: image, resource: resource._asResource(), userInitiated: false, priority: priority, storeToDownloadsPeerId: nil).startStrict()) } else if let _ = media as? TelegramMediaWebFile { //strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: context.account, image: image).startStrict()) } else if let file = media as? TelegramMediaFile { - let fetchSignal = messageMediaFileInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), file: file, userInitiated: false, priority: priority) + let fetchSignal = messageMediaFileInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false, threadId: nil), file: file, userInitiated: false, priority: priority) context.fetchDisposable.set(fetchSignal.startStrict()) } } else if case .prefetch = automaticDownload, mediaItem.media.peer.id.namespace != Namespaces.Peer.SecretChat { if let file = media as? TelegramMediaFile, let _ = file.size { - context.fetchDisposable.set(preloadVideoResource(postbox: self.account.postbox, userLocation: .peer(mediaItem.media.index.id.peerId), userContentType: MediaResourceUserContentType(file: file), resourceReference: FileMediaReference.message(message: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), media: file).resourceReference(file.resource), duration: 4.0).startStrict()) + context.fetchDisposable.set(preloadVideoResource(postbox: self.account.postbox, userLocation: .peer(mediaItem.media.index.id.peerId), userContentType: MediaResourceUserContentType(file: file), resourceReference: FileMediaReference.message(message: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false, threadId: nil), media: file).resourceReference(file.resource), duration: 4.0).startStrict()) } } }