[WIP] Quick replies

This commit is contained in:
Isaac 2024-02-23 15:26:33 +04:00
parent 4dbe7344e5
commit 6ed9cef40a
82 changed files with 2999 additions and 1149 deletions

View file

@ -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] = []

View file

@ -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])

View file

@ -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,

View file

@ -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<GalleryMessageHistoryView?, NoError> in

View file

@ -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

View file

@ -1,6 +1,6 @@
import Foundation
public enum AdditionalMessageHistoryViewData {
public enum AdditionalMessageHistoryViewData: Equatable {
case cachedPeerData(PeerId)
case cachedPeerDataMessages(PeerId)
case peerChatState(PeerId)

View file

@ -1029,7 +1029,7 @@ final class InternalStoreMessage {
}
}
public enum MessageIdNamespaces {
public enum MessageIdNamespaces: Equatable {
case all
case just(Set<MessageId.Namespace>)
case not(Set<MessageId.Namespace>)
@ -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
}
}

View file

@ -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<Int32>?
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<Int32>?,
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

View file

@ -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()

View file

@ -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

View file

@ -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<PeerId>()
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 {

View file

@ -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() {

View file

@ -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<Int32>?
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<Int32>? = 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: []
)
}
}

View file

@ -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:

View file

@ -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
}
}
}
}

View file

@ -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
}
}
}
}

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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 {

View file

@ -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

View file

@ -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])

View file

@ -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<Api.Bool>) {
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<Api.Bool>) {
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<Api.Bool>) {
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<Api.Bool>) {
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<Api.Bool>) {
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<Api.Updates>) {
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<Api.Updates>) {
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<Api.Updates>) {
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<Api.Updates>) {
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<Api.Updates>) {
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<Api.Updates>) {
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<Api.Updates>) {
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<Api.Updates>) {
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<Api.Updates>) {
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<Api.Updates>) {
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?

View file

@ -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)

View file

@ -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):

View file

@ -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
}()

View file

@ -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)))
}

View file

@ -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)

View file

@ -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
}

View file

@ -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

View file

@ -61,16 +61,30 @@ private func pollMessages(entries: [MessageHistoryEntry]) -> (Set<MessageId>, [M
return (messageIds, messages)
}
private func fetchWebpage(account: Account, messageId: MessageId) -> Signal<Void, NoError> {
private func fetchWebpage(account: Account, messageId: MessageId, threadId: Int64?) -> Signal<Void, NoError> {
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<Api.messages.Messages, MTRpcError>
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<Void
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
for message in messages {
if let storeMessage = StoreMessage(apiMessage: message, accountPeerId: accountPeerId, peerIsForum: peer.isForum, namespace: isScheduledMessage ? Namespaces.Message.ScheduledCloud : Namespaces.Message.Cloud) {
if let storeMessage = StoreMessage(apiMessage: message, accountPeerId: accountPeerId, peerIsForum: peer.isForum, namespace: targetMessageNamespace) {
var webpage: TelegramMediaWebpage?
for media in storeMessage.media {
if let media = media as? TelegramMediaWebpage {
@ -292,7 +306,7 @@ public final class AccountViewTracker {
private var nextUpdatedExtendedMediaDisposableId: Int32 = 0
private var updatedExtendedMediaDisposables = DisposableDict<Int32>()
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<MessageId>, localWebpages: [MessageId: (MediaId, String)]) {
private func updatePendingWebpages(viewId: Int32, threadId: Int64?, messageIds: Set<MessageId>, 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<MessageId>) {
public func updateUnsupportedMediaForMessageIds(messageIds: Set<MessageAndThreadId>) {
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<Int32>? = 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<Int32>? = 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()

View file

@ -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

View file

@ -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 {

View file

@ -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<Void, NoError> {
return postbox.transaction { transaction -> Signal<Void, NoError> in
var signal: Signal<ValidatedMessages, MTRpcError>
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<ValidatedMessages, MTRpcError>, previous: [MessageId: Message], messageNamespace: MessageId.Namespace) -> Signal<Void, NoError> {
return signal
|> map(Optional.init)

View file

@ -127,10 +127,14 @@ func managedCloudChatRemoveMessagesOperations(postbox: Postbox, network: Network
private func removeMessages(postbox: Postbox, network: Network, stateManager: AccountStateManager, peer: Peer, operation: CloudChatRemoveMessagesOperation) -> Signal<Void, NoError> {
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<Void, NoError> = .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<Void, NoError> 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<Void, NoError> = .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<Void, NoError> {
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<Api.Bool?, Bool> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Void, Bool> in
return .fail(true)
}
return (signal |> restart)
|> `catch` { _ -> Signal<Void, NoError> 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<Api.messages.AffectedHistory?, Bool> in
return .fail(true)
return .single(nil)
}
|> mapToSignal { result -> Signal<Void, Bool> in
if let result = result {

View file

@ -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
}

View file

@ -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<Void, NoError> {
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<Void, NoError> {
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

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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) })
}
}
}

View file

@ -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 {

View file

@ -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")
}
}
}
}

View file

@ -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<Int32> = Set([Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal])
public static let allQuickReply: Set<Int32> = Set([Namespaces.Message.QuickReplyCloud, Namespaces.Message.QuickReplyLocal])
public static let allNonRegular: Set<Int32> = 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 {

View file

@ -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<Never, NoError> = 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<QuickReplyMessageShortcutsState, NoError> {
return _internal_shortcutMessages(account: self.account)
public func shortcutMessageList() -> Signal<ShortcutMessageList, NoError> {
return _internal_shortcutMessageList(account: self.account)
}
public func updateShortcutMessages(state: QuickReplyMessageShortcutsState) {
let _ = _internal_updateShortcutMessages(account: self.account, state: state).startStandalone()
public func keepShortcutMessageListUpdated() -> Signal<Never, NoError> {
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()
}
}
}

View file

@ -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)

View file

@ -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<QuickReplyMessageShortcutsState, NoError> {
func _internal_quickReplyMessageShortcutsState(account: Account) -> Signal<QuickReplyMessageShortcutsState?, NoError> {
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<Never, NoError> {
return account.postbox.transaction { transaction -> Void in
func _internal_keepShortcutMessagesUpdated(account: Account) -> Signal<Never, NoError> {
let updateSignal = _internal_shortcutMessageList(account: account)
|> take(1)
|> mapToSignal { list -> Signal<Never, NoError> 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<Api.messages.QuickReplies?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> 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<ShortcutMessageList, NoError> {
return _internal_quickReplyMessageShortcutsState(account: account)
|> distinctUntilChanged
|> mapToSignal { state -> Signal<ShortcutMessageList, NoError> 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<Never, NoError> {
let remoteApply = account.network.request(Api.functions.messages.editQuickReplyShortcut(shortcutId: id, shortcut: shortcut))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> mapToSignal { _ -> Signal<Never, NoError> 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<Never, NoError> {
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<Never, NoError> {
let remoteApply = account.network.request(Api.functions.messages.reorderQuickReplies(order: ids))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> mapToSignal { _ -> Signal<Never, NoError> 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<Never, NoError> {
return account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
}
|> mapToSignal { peer -> Signal<Never, NoError> 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<Api.Updates?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> 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))
}
}

View file

@ -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

View file

@ -584,12 +584,18 @@ func _internal_downloadMessage(accountPeerId: PeerId, postbox: Postbox, network:
}
func fetchRemoteMessage(accountPeerId: PeerId, postbox: Postbox, source: FetchMessageHistoryHoleSource, message: MessageReference) -> Signal<Message?, NoError> {
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<Api.messages.Messages, MTRpcError>
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)]))

View file

@ -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

View file

@ -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) {

View file

@ -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

View file

@ -181,6 +181,36 @@ func messagesIdsGroupedByPeerId(_ ids: ReferencedReplyMessageIds) -> [PeerId: Re
return dict
}
func messagesIdsGroupedByPeerId(_ ids: Set<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 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

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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
}

View file

@ -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 {

View file

@ -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

View file

@ -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 {

View file

@ -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())

View file

@ -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<Impl>
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)
}
}
}

View file

@ -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) {

View file

@ -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<Empty>
let insets: UIEdgeInsets
init(
theme: PresentationTheme,
content: AnyComponentWithIdentity<Empty>,
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<Empty>()
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<Empty>, 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<Empty>()
}
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<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View file

@ -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
}
}

View file

@ -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<Bool, NoError> 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<Empty>()
private var navigationHeight: CGFloat?
private var selectionPanel: ComponentView<Empty>?
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<Int32>()
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<NavigationButtonComponentEnvironment>] = []
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<Empty>
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<Empty>
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
)
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "quickrepliesdemo.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -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

View file

@ -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)

View file

@ -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<EngineMessage?, NoError>
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

View file

@ -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()
}
})
}

View file

@ -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)
}

View file

@ -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)

View file

@ -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

View file

@ -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) })
}
}

View file

@ -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 {

View file

@ -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)
}
}

View file

@ -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<MessageId>) -> Void)?
var process: ((Set<MessageAndThreadId>) -> Void)?
private var timer: SwiftSignalKit.Timer?
private var processedList: [MessageId] = []
private var processed: [MessageId: Double] = [:]
private var buffer = Set<MessageId>()
private var processedList: [MessageAndThreadId] = []
private var processed: [MessageAndThreadId: Double] = [:]
private var buffer = Set<MessageAndThreadId>()
init(delay: Double = 1.0, submitInterval: Double? = nil) {
self.delay = delay
self.submitInterval = submitInterval
}
func setProcess(process: @escaping (Set<MessageId>) -> Void) {
func setProcess(process: @escaping (Set<MessageAndThreadId>) -> 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<MessageId>()
private var currentIds = Set<MessageAndThreadId>()
var process: ((Set<MessageId>) -> Void)?
var process: ((Set<MessageAndThreadId>) -> Void)?
private let timer: SwiftSignalKit.Timer
@ -107,13 +107,13 @@ final class ChatMessageVisibleThrottledProcessingManager {
self.timer.invalidate()
}
func setProcess(process: @escaping (Set<MessageId>) -> Void) {
func setProcess(process: @escaping (Set<MessageAndThreadId>) -> Void) {
self.queue.async {
self.process = process
}
}
func update(_ ids: Set<MessageId>) {
func update(_ ids: Set<MessageAndThreadId>) {
self.queue.async {
if self.currentIds != ids {
self.currentIds = ids

View file

@ -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 {

View file

@ -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

View file

@ -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 {

View file

@ -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())
}
}
}