import Foundation import UIKit import Postbox import TelegramCore import TextFormat import AccountContext import SwiftSignalKit import AudioWaveform public enum ChatTextInputMediaRecordingButtonMode: Int32 { case audio = 0 case video = 1 } public struct ChatInterfaceSelectionState: Codable, Equatable { public let selectedIds: Set public static func ==(lhs: ChatInterfaceSelectionState, rhs: ChatInterfaceSelectionState) -> Bool { return lhs.selectedIds == rhs.selectedIds } public init(selectedIds: Set) { self.selectedIds = selectedIds } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: StringCodingKey.self) if let data = try? container.decodeIfPresent(Data.self, forKey: "i") { self.selectedIds = Set(EngineMessage.Id.decodeArrayFromData(data)) } else { self.selectedIds = Set() } } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: StringCodingKey.self) let data = EngineMessage.Id.encodeArrayToData(Array(selectedIds)) try container.encode(data, forKey: "i") } } public struct ChatEditMessageState: Codable, Equatable { public var messageId: EngineMessage.Id public var inputState: ChatTextInputState public var disableUrlPreviews: [String] public var inputTextMaxLength: Int32? public var mediaCaptionIsAbove: Bool? public init(messageId: EngineMessage.Id, inputState: ChatTextInputState, disableUrlPreviews: [String], inputTextMaxLength: Int32?, mediaCaptionIsAbove: Bool?) { self.messageId = messageId self.inputState = inputState self.disableUrlPreviews = disableUrlPreviews self.inputTextMaxLength = inputTextMaxLength self.mediaCaptionIsAbove = mediaCaptionIsAbove } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: StringCodingKey.self) self.messageId = EngineMessage.Id( peerId: EnginePeer.Id((try? container.decode(Int64.self, forKey: "mp")) ?? 0), namespace: (try? container.decode(Int32.self, forKey: "mn")) ?? 0, id: (try? container.decode(Int32.self, forKey: "mi")) ?? 0 ) if let inputState = try? container.decode(ChatTextInputState.self, forKey: "is") { self.inputState = inputState } else { self.inputState = ChatTextInputState() } if let disableUrlPreviews = try? container.decodeIfPresent([String].self, forKey: "dupl") { self.disableUrlPreviews = disableUrlPreviews } else { if let disableUrlPreview = try? container.decodeIfPresent(String.self, forKey: "dup") { self.disableUrlPreviews = [disableUrlPreview] } else { self.disableUrlPreviews = [] } } self.inputTextMaxLength = try? container.decodeIfPresent(Int32.self, forKey: "tl") self.mediaCaptionIsAbove = try? container.decodeIfPresent(Bool.self, forKey: "mediaCaptionIsAbove") } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: StringCodingKey.self) try container.encode(self.messageId.peerId.toInt64(), forKey: "mp") try container.encode(self.messageId.namespace, forKey: "mn") try container.encode(self.messageId.id, forKey: "mi") try container.encode(self.inputState, forKey: "is") try container.encode(self.disableUrlPreviews, forKey: "dupl") try container.encodeIfPresent(self.inputTextMaxLength, forKey: "tl") try container.encodeIfPresent(self.mediaCaptionIsAbove, forKey: "mediaCaptionIsAbove") } public static func ==(lhs: ChatEditMessageState, rhs: ChatEditMessageState) -> Bool { return lhs.messageId == rhs.messageId && lhs.inputState == rhs.inputState && lhs.disableUrlPreviews == rhs.disableUrlPreviews && lhs.inputTextMaxLength == rhs.inputTextMaxLength && lhs.mediaCaptionIsAbove == rhs.mediaCaptionIsAbove } public func withUpdatedInputState(_ inputState: ChatTextInputState) -> ChatEditMessageState { return ChatEditMessageState(messageId: self.messageId, inputState: inputState, disableUrlPreviews: self.disableUrlPreviews, inputTextMaxLength: self.inputTextMaxLength, mediaCaptionIsAbove: self.mediaCaptionIsAbove) } public func withUpdatedDisableUrlPreviews(_ disableUrlPreviews: [String]) -> ChatEditMessageState { return ChatEditMessageState(messageId: self.messageId, inputState: self.inputState, disableUrlPreviews: disableUrlPreviews, inputTextMaxLength: self.inputTextMaxLength, mediaCaptionIsAbove: self.mediaCaptionIsAbove) } } public struct ChatInterfaceMessageActionsState: Codable, Equatable { public var closedButtonKeyboardMessageId: EngineMessage.Id? public var dismissedButtonKeyboardMessageId: EngineMessage.Id? public var processedSetupReplyMessageId: EngineMessage.Id? public var closedPinnedMessageId: EngineMessage.Id? public var closedPeerSpecificPackSetup: Bool = false public var dismissedAddContactPhoneNumber: String? public var isEmpty: Bool { return self.closedButtonKeyboardMessageId == nil && self.dismissedButtonKeyboardMessageId == nil && self.processedSetupReplyMessageId == nil && self.closedPinnedMessageId == nil && self.closedPeerSpecificPackSetup == false && self.dismissedAddContactPhoneNumber == nil } public init() { self.closedButtonKeyboardMessageId = nil self.dismissedButtonKeyboardMessageId = nil self.processedSetupReplyMessageId = nil self.closedPinnedMessageId = nil self.closedPeerSpecificPackSetup = false self.dismissedAddContactPhoneNumber = nil } public init(closedButtonKeyboardMessageId: EngineMessage.Id?, dismissedButtonKeyboardMessageId: EngineMessage.Id?, processedSetupReplyMessageId: EngineMessage.Id?, closedPinnedMessageId: EngineMessage.Id?, closedPeerSpecificPackSetup: Bool, dismissedAddContactPhoneNumber: String?) { self.closedButtonKeyboardMessageId = closedButtonKeyboardMessageId self.dismissedButtonKeyboardMessageId = dismissedButtonKeyboardMessageId self.processedSetupReplyMessageId = processedSetupReplyMessageId self.closedPinnedMessageId = closedPinnedMessageId self.closedPeerSpecificPackSetup = closedPeerSpecificPackSetup self.dismissedAddContactPhoneNumber = dismissedAddContactPhoneNumber } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: StringCodingKey.self) if let closedMessageIdPeerId = try? container.decodeIfPresent(Int64.self, forKey: "cb.p"), let closedMessageIdNamespace = try? container.decodeIfPresent(Int32.self, forKey: "cb.n"), let closedMessageIdId = try? container.decodeIfPresent(Int32.self, forKey: "cb.i") { self.closedButtonKeyboardMessageId = EngineMessage.Id(peerId: EnginePeer.Id(closedMessageIdPeerId), namespace: closedMessageIdNamespace, id: closedMessageIdId) } else { self.closedButtonKeyboardMessageId = nil } if let messageIdPeerId = try? container.decodeIfPresent(Int64.self, forKey: "dismissedbuttons.p"), let messageIdNamespace = try? container.decodeIfPresent(Int32.self, forKey: "dismissedbuttons.n"), let messageIdId = try? container.decodeIfPresent(Int32.self, forKey: "dismissedbuttons.i") { self.dismissedButtonKeyboardMessageId = EngineMessage.Id(peerId: EnginePeer.Id(messageIdPeerId), namespace: messageIdNamespace, id: messageIdId) } else { self.dismissedButtonKeyboardMessageId = nil } if let processedMessageIdPeerId = try? container.decodeIfPresent(Int64.self, forKey: "pb.p"), let processedMessageIdNamespace = try? container.decodeIfPresent(Int32.self, forKey: "pb.n"), let processedMessageIdId = try? container.decodeIfPresent(Int32.self, forKey: "pb.i") { self.processedSetupReplyMessageId = EngineMessage.Id(peerId: EnginePeer.Id(processedMessageIdPeerId), namespace: processedMessageIdNamespace, id: processedMessageIdId) } else { self.processedSetupReplyMessageId = nil } if let closedPinnedMessageIdPeerId = try? container.decodeIfPresent(Int64.self, forKey: "cp.p"), let closedPinnedMessageIdNamespace = try? container.decodeIfPresent(Int32.self, forKey: "cp.n"), let closedPinnedMessageIdId = try? container.decodeIfPresent(Int32.self, forKey: "cp.i") { self.closedPinnedMessageId = EngineMessage.Id(peerId: EnginePeer.Id(closedPinnedMessageIdPeerId), namespace: closedPinnedMessageIdNamespace, id: closedPinnedMessageIdId) } else { self.closedPinnedMessageId = nil } self.closedPeerSpecificPackSetup = ((try? container.decode(Int32.self, forKey: "cpss")) ?? 0) != 0 self.dismissedAddContactPhoneNumber = try? container.decodeIfPresent(String.self, forKey: "dismissedAddContactPhoneNumber") } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: StringCodingKey.self) if let closedButtonKeyboardMessageId = self.closedButtonKeyboardMessageId { try container.encode(closedButtonKeyboardMessageId.peerId.toInt64(), forKey: "cb.p") try container.encode(closedButtonKeyboardMessageId.namespace, forKey: "cb.n") try container.encode(closedButtonKeyboardMessageId.id, forKey: "cb.i") } else { try container.encodeNil(forKey: "cb.p") try container.encodeNil(forKey: "cb.n") try container.encodeNil(forKey: "cb.i") } if let dismissedButtonKeyboardMessageId = self.dismissedButtonKeyboardMessageId { try container.encode(dismissedButtonKeyboardMessageId.peerId.toInt64(), forKey: "dismissedbuttons.p") try container.encode(dismissedButtonKeyboardMessageId.namespace, forKey: "dismissedbuttons.n") try container.encode(dismissedButtonKeyboardMessageId.id, forKey: "dismissedbuttons.i") } else { try container.encodeNil(forKey: "dismissedbuttons.p") try container.encodeNil(forKey: "dismissedbuttons.n") try container.encodeNil(forKey: "dismissedbuttons.i") } if let processedSetupReplyMessageId = self.processedSetupReplyMessageId { try container.encode(processedSetupReplyMessageId.peerId.toInt64(), forKey: "pb.p") try container.encode(processedSetupReplyMessageId.namespace, forKey: "pb.n") try container.encode(processedSetupReplyMessageId.id, forKey: "pb.i") } else { try container.encodeNil(forKey: "pb.p") try container.encodeNil(forKey: "pb.n") try container.encodeNil(forKey: "pb.i") } if let closedPinnedMessageId = self.closedPinnedMessageId { try container.encode(closedPinnedMessageId.peerId.toInt64(), forKey: "cp.p") try container.encode(closedPinnedMessageId.namespace, forKey: "cp.n") try container.encode(closedPinnedMessageId.id, forKey: "cp.i") } else { try container.encodeNil(forKey: "cp.p") try container.encodeNil(forKey: "cp.n") try container.encodeNil(forKey: "cp.i") } try container.encode((self.closedPeerSpecificPackSetup ? 1 : 0) as Int32, forKey: "cpss") if let dismissedAddContactPhoneNumber = self.dismissedAddContactPhoneNumber { try container.encode(dismissedAddContactPhoneNumber, forKey: "dismissedAddContactPhoneNumber") } else { try container.encodeNil(forKey: "dismissedAddContactPhoneNumber") } } } public struct ChatInterfaceHistoryScrollState: Codable, Equatable { public let messageIndex: EngineMessage.Index public let relativeOffset: Double public init(messageIndex: EngineMessage.Index, relativeOffset: Double) { self.messageIndex = messageIndex self.relativeOffset = relativeOffset } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: StringCodingKey.self) self.messageIndex = EngineMessage.Index( id: EngineMessage.Id( peerId: EnginePeer.Id((try? container.decode(Int64.self, forKey: "m.p")) ?? 0), namespace: (try? container.decode(Int32.self, forKey: "m.n")) ?? 0, id: (try? container.decode(Int32.self, forKey: "m.i")) ?? 0 ), timestamp: (try? container.decode(Int32.self, forKey: "m.t")) ?? 0 ) self.relativeOffset = (try? container.decode(Double.self, forKey: "ro")) ?? 0.0 } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: StringCodingKey.self) try container.encode(self.messageIndex.timestamp, forKey: "m.t") try container.encode(self.messageIndex.id.peerId.toInt64(), forKey: "m.p") try container.encode(self.messageIndex.id.namespace, forKey: "m.n") try container.encode(self.messageIndex.id.id, forKey: "m.i") try container.encode(self.relativeOffset, forKey: "ro") } public static func ==(lhs: ChatInterfaceHistoryScrollState, rhs: ChatInterfaceHistoryScrollState) -> Bool { if lhs.messageIndex != rhs.messageIndex { return false } if !lhs.relativeOffset.isEqual(to: rhs.relativeOffset) { return false } return true } } public enum ChatInterfaceMediaDraftState: Codable, Equatable { enum DecodingError: Error { case generic } public struct Audio: Codable, Equatable { public let resource: LocalFileMediaResource public let fileSize: Int32 public let duration: Int32 public let waveform: AudioWaveform public init( resource: LocalFileMediaResource, fileSize: Int32, duration: Int32, waveform: AudioWaveform ) { self.resource = resource self.fileSize = fileSize self.duration = duration self.waveform = waveform } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: StringCodingKey.self) let resourceData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: "r") self.resource = LocalFileMediaResource(decoder: PostboxDecoder(buffer: MemoryBuffer(data: resourceData.data))) self.fileSize = try container.decode(Int32.self, forKey: "s") self.duration = try container.decode(Int32.self, forKey: "d") let waveformData = try container.decode(Data.self, forKey: "wd") let waveformPeak = try container.decode(Int32.self, forKey: "wp") self.waveform = AudioWaveform(samples: waveformData, peak: waveformPeak) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: StringCodingKey.self) try container.encode(PostboxEncoder().encodeObjectToRawData(self.resource), forKey: "r") try container.encode(self.fileSize, forKey: "s") try container.encode(self.duration, forKey: "d") try container.encode(self.waveform.samples, forKey: "wd") try container.encode(self.waveform.peak, forKey: "wp") } public static func ==(lhs: Audio, rhs: Audio) -> Bool { if !lhs.resource.isEqual(to: rhs.resource) { return false } if lhs.duration != rhs.duration { return false } if lhs.fileSize != rhs.fileSize { return false } if lhs.waveform != rhs.waveform { return false } return true } } public struct Video: Codable, Equatable { public let duration: Int32 public let frames: [UIImage] public let framesUpdateTimestamp: Double public let trimRange: Range? public init( duration: Int32, frames: [UIImage], framesUpdateTimestamp: Double, trimRange: Range? ) { self.duration = duration self.frames = frames self.framesUpdateTimestamp = framesUpdateTimestamp self.trimRange = trimRange } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: StringCodingKey.self) self.duration = try container.decode(Int32.self, forKey: "d") self.frames = [] self.framesUpdateTimestamp = try container.decode(Double.self, forKey: "fu") if let trimLowerBound = try container.decodeIfPresent(Double.self, forKey: "tl"), let trimUpperBound = try container.decodeIfPresent(Double.self, forKey: "tu") { self.trimRange = trimLowerBound ..< trimUpperBound } else { self.trimRange = nil } } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: StringCodingKey.self) try container.encode(self.duration, forKey: "d") try container.encode(self.framesUpdateTimestamp, forKey: "fu") if let trimRange = self.trimRange { try container.encode(trimRange.lowerBound, forKey: "tl") try container.encode(trimRange.upperBound, forKey: "tu") } } public static func ==(lhs: Video, rhs: Video) -> Bool { if lhs.duration != rhs.duration { return false } if lhs.framesUpdateTimestamp != rhs.framesUpdateTimestamp { return false } if lhs.trimRange != rhs.trimRange { return false } return true } } case audio(Audio) case video(Video) enum MediaType: Int32 { case audio case video } public var contentType: EngineChatList.MediaDraftContentType { switch self { case .audio: return .audio case .video: return .video } } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: StringCodingKey.self) guard let mediaType = MediaType(rawValue: try container.decode(Int32.self, forKey: "t")) else { throw DecodingError.generic } switch mediaType { case .audio: self = .audio(try container.decode(Audio.self, forKey: "a")) case .video: self = .video(try container.decode(Video.self, forKey: "v")) } } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: StringCodingKey.self) switch self { case let .audio(audio): try container.encode(MediaType.audio.rawValue, forKey: "t") try container.encode(audio, forKey: "a") case let .video(video): try container.encode(MediaType.video.rawValue, forKey: "t") try container.encode(video, forKey: "v") } } } public final class ChatInterfaceState: Codable, Equatable { public struct ReplyMessageSubject: Codable, Equatable { public var messageId: EngineMessage.Id public var quote: EngineMessageReplyQuote? public init(messageId: EngineMessage.Id, quote: EngineMessageReplyQuote?) { self.messageId = messageId self.quote = quote } public var subjectModel: EngineMessageReplySubject { return EngineMessageReplySubject( messageId: self.messageId, quote: self.quote ) } } public let timestamp: Int32 public let composeInputState: ChatTextInputState public let composeDisableUrlPreviews: [String] public let replyMessageSubject: ReplyMessageSubject? public let forwardMessageIds: [EngineMessage.Id]? public let forwardOptionsState: ChatInterfaceForwardOptionsState? public let editMessage: ChatEditMessageState? public let selectionState: ChatInterfaceSelectionState? public let messageActionsState: ChatInterfaceMessageActionsState public let historyScrollState: ChatInterfaceHistoryScrollState? public let mediaRecordingMode: ChatTextInputMediaRecordingButtonMode public let mediaDraftState: ChatInterfaceMediaDraftState? public let silentPosting: Bool public let inputLanguage: String? public let sendMessageEffect: Int64? public var synchronizeableInputState: SynchronizeableChatInputState? { if self.composeInputState.inputText.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && self.replyMessageSubject == nil { return nil } else { let sourceText = expandedInputStateAttributedString(self.composeInputState.inputText) return SynchronizeableChatInputState(replySubject: self.replyMessageSubject?.subjectModel, text: sourceText.string, entities: generateChatInputTextEntities(sourceText), timestamp: self.timestamp, textSelection: self.composeInputState.selectionRange, messageEffectId: self.sendMessageEffect) } } public func withUpdatedSynchronizeableInputState(_ state: SynchronizeableChatInputState?) -> ChatInterfaceState { var result = self.withUpdatedComposeInputState(ChatTextInputState(inputText: chatInputStateStringWithAppliedEntities(state?.text ?? "", entities: state?.entities ?? []))).withUpdatedReplyMessageSubject((state?.replySubject).flatMap { return ReplyMessageSubject( messageId: $0.messageId, quote: $0.quote ) }) if let timestamp = state?.timestamp { result = result.withUpdatedTimestamp(timestamp) } return result } public var historyScrollMessageIndex: EngineMessage.Index? { return self.historyScrollState?.messageIndex } public var effectiveInputState: ChatTextInputState { if let editMessage = self.editMessage { return editMessage.inputState } else { return self.composeInputState } } public init() { self.timestamp = 0 self.composeInputState = ChatTextInputState() self.composeDisableUrlPreviews = [] self.replyMessageSubject = nil self.forwardMessageIds = nil self.forwardOptionsState = nil self.editMessage = nil self.selectionState = nil self.messageActionsState = ChatInterfaceMessageActionsState() self.historyScrollState = nil self.mediaRecordingMode = .audio self.mediaDraftState = nil self.silentPosting = false self.inputLanguage = nil self.sendMessageEffect = nil } public init(timestamp: Int32, composeInputState: ChatTextInputState, composeDisableUrlPreviews: [String], replyMessageSubject: ReplyMessageSubject?, forwardMessageIds: [EngineMessage.Id]?, forwardOptionsState: ChatInterfaceForwardOptionsState?, editMessage: ChatEditMessageState?, selectionState: ChatInterfaceSelectionState?, messageActionsState: ChatInterfaceMessageActionsState, historyScrollState: ChatInterfaceHistoryScrollState?, mediaRecordingMode: ChatTextInputMediaRecordingButtonMode, mediaDraftState: ChatInterfaceMediaDraftState?, silentPosting: Bool, inputLanguage: String?, sendMessageEffect: Int64?) { self.timestamp = timestamp self.composeInputState = composeInputState self.composeDisableUrlPreviews = composeDisableUrlPreviews self.replyMessageSubject = replyMessageSubject self.forwardMessageIds = forwardMessageIds self.forwardOptionsState = forwardOptionsState self.editMessage = editMessage self.selectionState = selectionState self.messageActionsState = messageActionsState self.historyScrollState = historyScrollState self.mediaRecordingMode = mediaRecordingMode self.mediaDraftState = mediaDraftState self.silentPosting = silentPosting self.inputLanguage = inputLanguage self.sendMessageEffect = sendMessageEffect } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: StringCodingKey.self) self.timestamp = (try? container.decode(Int32.self, forKey: "ts")) ?? 0 if let inputState = try? container.decode(ChatTextInputState.self, forKey: "is") { self.composeInputState = inputState } else { self.composeInputState = ChatTextInputState() } if let composeDisableUrlPreviews = try? container.decodeIfPresent([String].self, forKey: "dupl") { self.composeDisableUrlPreviews = composeDisableUrlPreviews } else if let composeDisableUrlPreview = try? container.decodeIfPresent(String.self, forKey: "dup") { self.composeDisableUrlPreviews = [composeDisableUrlPreview] } else { self.composeDisableUrlPreviews = [] } if let replyMessageSubject = try? container.decodeIfPresent(ReplyMessageSubject.self, forKey: "replyMessageSubject") { self.replyMessageSubject = replyMessageSubject } else { let replyMessageIdPeerId: Int64? = try? container.decodeIfPresent(Int64.self, forKey: "r.p") let replyMessageIdNamespace: Int32? = try? container.decodeIfPresent(Int32.self, forKey: "r.n") let replyMessageIdId: Int32? = try? container.decodeIfPresent(Int32.self, forKey: "r.i") if let replyMessageIdPeerId = replyMessageIdPeerId, let replyMessageIdNamespace = replyMessageIdNamespace, let replyMessageIdId = replyMessageIdId { self.replyMessageSubject = ReplyMessageSubject(messageId: EngineMessage.Id(peerId: EnginePeer.Id(replyMessageIdPeerId), namespace: replyMessageIdNamespace, id: replyMessageIdId), quote: nil) } else { self.replyMessageSubject = nil } } if let forwardMessageIdsData = try? container.decodeIfPresent(Data.self, forKey: "fm") { self.forwardMessageIds = EngineMessage.Id.decodeArrayFromData(forwardMessageIdsData) } else { self.forwardMessageIds = nil } if let forwardOptionsState = try? container.decodeIfPresent(ChatInterfaceForwardOptionsState.self, forKey: "fo") { self.forwardOptionsState = forwardOptionsState } else { self.forwardOptionsState = nil } if let editMessage = try? container.decodeIfPresent(ChatEditMessageState.self, forKey: "em") { self.editMessage = editMessage } else { self.editMessage = nil } if let selectionState = try? container.decodeIfPresent(ChatInterfaceSelectionState.self, forKey: "ss") { self.selectionState = selectionState } else { self.selectionState = nil } if let messageActionsState = try? container.decodeIfPresent(ChatInterfaceMessageActionsState.self, forKey: "as") { self.messageActionsState = messageActionsState } else { self.messageActionsState = ChatInterfaceMessageActionsState() } self.historyScrollState = try? container.decodeIfPresent(ChatInterfaceHistoryScrollState.self, forKey: "hss") self.mediaRecordingMode = ChatTextInputMediaRecordingButtonMode(rawValue: (try? container.decodeIfPresent(Int32.self, forKey: "mrm")) ?? 0) ?? .audio if let mediaDraftState = try? container.decodeIfPresent(ChatInterfaceMediaDraftState.self, forKey: "mds") { self.mediaDraftState = mediaDraftState } else { self.mediaDraftState = nil } self.silentPosting = ((try? container.decode(Int32.self, forKey: "sip")) ?? 0) != 0 self.inputLanguage = try? container.decodeIfPresent(String.self, forKey: "inputLanguage") self.sendMessageEffect = try? container.decodeIfPresent(Int64.self, forKey: "sendMessageEffect") } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: StringCodingKey.self) try container.encode(self.timestamp, forKey: "ts") try container.encode(self.composeInputState, forKey: "is") try container.encode(self.composeDisableUrlPreviews, forKey: "dup;") if let replyMessageSubject = self.replyMessageSubject { try container.encode(replyMessageSubject, forKey: "replyMessageSubject") } else { try container.encodeNil(forKey: "replyMessageSubject") } if let forwardMessageIds = self.forwardMessageIds { try container.encode(EngineMessage.Id.encodeArrayToData(forwardMessageIds), forKey: "fm") } else { try container.encodeNil(forKey: "fm") } if let forwardOptionsState = self.forwardOptionsState { try container.encode(forwardOptionsState, forKey: "fo") } else { try container.encodeNil(forKey: "fo") } if let editMessage = self.editMessage { try container.encode(editMessage, forKey: "em") } else { try container.encodeNil(forKey: "em") } if let selectionState = self.selectionState { try container.encode(selectionState, forKey: "ss") } else { try container.encodeNil(forKey: "ss") } if self.messageActionsState.isEmpty { try container.encodeNil(forKey: "as") } else { try container.encode(self.messageActionsState, forKey: "as") } if let historyScrollState = self.historyScrollState { try container.encode(historyScrollState, forKey: "hss") } else { try container.encodeNil(forKey: "hss") } try container.encode(self.mediaRecordingMode.rawValue, forKey: "mrm") if let mediaDraftState = self.mediaDraftState { try container.encode(mediaDraftState, forKey: "mds") } else { try container.encodeNil(forKey: "mds") } try container.encode((self.silentPosting ? 1 : 0) as Int32, forKey: "sip") if let inputLanguage = self.inputLanguage { try container.encode(inputLanguage, forKey: "inputLanguage") } else { try container.encodeNil(forKey: "inputLanguage") } try container.encodeIfPresent(self.sendMessageEffect, forKey: "sendMessageEffect") } public static func ==(lhs: ChatInterfaceState, rhs: ChatInterfaceState) -> Bool { if lhs.composeDisableUrlPreviews != rhs.composeDisableUrlPreviews { return false } if let lhsForwardMessageIds = lhs.forwardMessageIds, let rhsForwardMessageIds = rhs.forwardMessageIds { if lhsForwardMessageIds != rhsForwardMessageIds { return false } } else if (lhs.forwardMessageIds != nil) != (rhs.forwardMessageIds != nil) { return false } if lhs.forwardOptionsState != rhs.forwardOptionsState { return false } if lhs.messageActionsState != rhs.messageActionsState { return false } if lhs.historyScrollState != rhs.historyScrollState { return false } if lhs.mediaRecordingMode != rhs.mediaRecordingMode { return false } if lhs.mediaDraftState != rhs.mediaDraftState { return false } if lhs.silentPosting != rhs.silentPosting { return false } if lhs.inputLanguage != rhs.inputLanguage { return false } if lhs.sendMessageEffect != rhs.sendMessageEffect { return false } return lhs.composeInputState == rhs.composeInputState && lhs.replyMessageSubject == rhs.replyMessageSubject && lhs.selectionState == rhs.selectionState && lhs.editMessage == rhs.editMessage } public func withUpdatedComposeInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { let updatedComposeInputState = inputState return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) } public func withUpdatedComposeDisableUrlPreviews(_ disableUrlPreviews: [String]) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: disableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) } public func withUpdatedEffectiveInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { var updatedEditMessage = self.editMessage var updatedComposeInputState = self.composeInputState if let editMessage = self.editMessage { updatedEditMessage = editMessage.withUpdatedInputState(inputState) } else { updatedComposeInputState = inputState } return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: updatedEditMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) } public func withUpdatedReplyMessageSubject(_ replyMessageSubject: ReplyMessageSubject?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) } public func withUpdatedForwardMessageIds(_ forwardMessageIds: [EngineMessage.Id]?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) } public func withUpdatedForwardOptionsState(_ forwardOptionsState: ChatInterfaceForwardOptionsState?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) } public func withUpdatedSelectedMessages(_ messageIds: [EngineMessage.Id]) -> ChatInterfaceState { var selectedIds = Set() if let selectionState = self.selectionState { selectedIds.formUnion(selectionState.selectedIds) } for messageId in messageIds { selectedIds.insert(messageId) } return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) } public func withToggledSelectedMessages(_ messageIds: [EngineMessage.Id], value: Bool) -> ChatInterfaceState { var selectedIds = Set() if let selectionState = self.selectionState { selectedIds.formUnion(selectionState.selectedIds) } for messageId in messageIds { if value { selectedIds.insert(messageId) } else { selectedIds.remove(messageId) } } return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) } public func withoutSelectionState() -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: nil, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) } public func withUpdatedTimestamp(_ timestamp: Int32) -> ChatInterfaceState { return ChatInterfaceState(timestamp: timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) } public func withUpdatedEditMessage(_ editMessage: ChatEditMessageState?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) } public func withUpdatedMessageActionsState(_ f: (ChatInterfaceMessageActionsState) -> ChatInterfaceMessageActionsState) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: f(self.messageActionsState), historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) } public func withUpdatedHistoryScrollState(_ historyScrollState: ChatInterfaceHistoryScrollState?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) } public func withUpdatedMediaRecordingMode(_ mediaRecordingMode: ChatTextInputMediaRecordingButtonMode) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) } public func withUpdatedMediaDraftState(_ mediaDraftState: ChatInterfaceMediaDraftState?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) } public func withUpdatedSilentPosting(_ silentPosting: Bool) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) } public func withUpdatedInputLanguage(_ inputLanguage: String?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: inputLanguage, sendMessageEffect: self.sendMessageEffect) } public func withUpdatedSendMessageEffect(_ sendMessageEffect: Int64?) -> ChatInterfaceState { return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: sendMessageEffect) } public static func parse(_ state: OpaqueChatInterfaceState) -> ChatInterfaceState { guard let opaqueData = state.opaqueData else { return ChatInterfaceState().withUpdatedSynchronizeableInputState(state.synchronizeableInputState) } guard var decodedState = try? EngineDecoder.decode(ChatInterfaceState.self, from: opaqueData) else { return ChatInterfaceState().withUpdatedSynchronizeableInputState(state.synchronizeableInputState) } decodedState = decodedState.withUpdatedSynchronizeableInputState(state.synchronizeableInputState) return decodedState } public static func update(engine: TelegramEngine, peerId: EnginePeer.Id, threadId: Int64?, _ f: @escaping (ChatInterfaceState) -> ChatInterfaceState) -> Signal { return engine.peers.getOpaqueChatInterfaceState(peerId: peerId, threadId: threadId) |> mapToSignal { previousOpaqueState -> Signal in let previousState = previousOpaqueState.flatMap(ChatInterfaceState.parse) let updatedState = f(previousState ?? ChatInterfaceState()) let updatedOpaqueData = try? EngineEncoder.encode(updatedState) var mediaDraftState: MediaDraftState? if let interfaceMediaDraftState = updatedState.mediaDraftState { mediaDraftState = MediaDraftState(contentType: interfaceMediaDraftState.contentType, timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)) } return engine.peers.setOpaqueChatInterfaceState( peerId: peerId, threadId: threadId, state: OpaqueChatInterfaceState( opaqueData: updatedOpaqueData, historyScrollMessageIndex: updatedState.historyScrollMessageIndex, mediaDraftState: mediaDraftState, synchronizeableInputState: updatedState.synchronizeableInputState )) } } }