2020-09-08 17:40:19 +01:00

489 lines
18 KiB
Swift

import Foundation
import UIKit
import Postbox
import TelegramCore
import SyncCore
import TextFormat
import AsyncDisplayKit
import Display
import SwiftSignalKit
import TelegramPresentationData
import TelegramUIPreferences
public final class ChatMessageItemAssociatedData: Equatable {
public enum ChannelDiscussionGroupStatus: Equatable {
case unknown
case known(PeerId?)
}
public let automaticDownloadPeerType: MediaAutoDownloadPeerType
public let automaticDownloadNetworkType: MediaAutoDownloadNetworkType
public let isRecentActions: Bool
public let isScheduledMessages: Bool
public let contactsPeerIds: Set<PeerId>
public let channelDiscussionGroup: ChannelDiscussionGroupStatus
public let animatedEmojiStickers: [String: [StickerPackItem]]
public let forcedResourceStatus: FileMediaResourceStatus?
public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, isScheduledMessages: Bool = false, contactsPeerIds: Set<PeerId> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil) {
self.automaticDownloadPeerType = automaticDownloadPeerType
self.automaticDownloadNetworkType = automaticDownloadNetworkType
self.isRecentActions = isRecentActions
self.isScheduledMessages = isScheduledMessages
self.contactsPeerIds = contactsPeerIds
self.channelDiscussionGroup = channelDiscussionGroup
self.animatedEmojiStickers = animatedEmojiStickers
self.forcedResourceStatus = forcedResourceStatus
}
public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool {
if lhs.automaticDownloadPeerType != rhs.automaticDownloadPeerType {
return false
}
if lhs.automaticDownloadNetworkType != rhs.automaticDownloadNetworkType {
return false
}
if lhs.isRecentActions != rhs.isRecentActions {
return false
}
if lhs.isScheduledMessages != rhs.isScheduledMessages {
return false
}
if lhs.contactsPeerIds != rhs.contactsPeerIds {
return false
}
if lhs.channelDiscussionGroup != rhs.channelDiscussionGroup {
return false
}
if lhs.animatedEmojiStickers != rhs.animatedEmojiStickers {
return false
}
if lhs.forcedResourceStatus != rhs.forcedResourceStatus {
return false
}
return true
}
}
public enum ChatControllerInteractionLongTapAction {
case url(String)
case mention(String)
case peerMention(PeerId, String)
case command(String)
case hashtag(String)
case timecode(Double, String)
case bankCard(String)
}
public enum ChatHistoryMessageSelection: Equatable {
case none
case selectable(selected: Bool)
public static func ==(lhs: ChatHistoryMessageSelection, rhs: ChatHistoryMessageSelection) -> Bool {
switch lhs {
case .none:
if case .none = rhs {
return true
} else {
return false
}
case let .selectable(selected):
if case .selectable(selected) = rhs {
return true
} else {
return false
}
}
}
}
public enum ChatControllerInitialBotStartBehavior {
case interactive
case automatic(returnToPeerId: PeerId, scheduled: Bool)
}
public struct ChatControllerInitialBotStart {
public let payload: String
public let behavior: ChatControllerInitialBotStartBehavior
public init(payload: String, behavior: ChatControllerInitialBotStartBehavior) {
self.payload = payload
self.behavior = behavior
}
}
public enum ChatControllerInteractionNavigateToPeer {
case `default`
case chat(textInputState: ChatTextInputState?, subject: ChatControllerSubject?, peekData: ChatPeekTimeout?)
case info
case withBotStartPayload(ChatControllerInitialBotStart)
}
public struct ChatTextInputState: PostboxCoding, Equatable {
public let inputText: NSAttributedString
public let selectionRange: Range<Int>
public static func ==(lhs: ChatTextInputState, rhs: ChatTextInputState) -> Bool {
return lhs.inputText.isEqual(to: rhs.inputText) && lhs.selectionRange == rhs.selectionRange
}
public init() {
self.inputText = NSAttributedString()
self.selectionRange = 0 ..< 0
}
public init(inputText: NSAttributedString, selectionRange: Range<Int>) {
self.inputText = inputText
self.selectionRange = selectionRange
}
public init(inputText: NSAttributedString) {
self.inputText = inputText
let length = inputText.length
self.selectionRange = length ..< length
}
public init(decoder: PostboxDecoder) {
self.inputText = ((decoder.decodeObjectForKey("at", decoder: { ChatTextInputStateText(decoder: $0) }) as? ChatTextInputStateText) ?? ChatTextInputStateText()).attributedText()
self.selectionRange = Int(decoder.decodeInt32ForKey("as0", orElse: 0)) ..< Int(decoder.decodeInt32ForKey("as1", orElse: 0))
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeObject(ChatTextInputStateText(attributedText: self.inputText), forKey: "at")
encoder.encodeInt32(Int32(self.selectionRange.lowerBound), forKey: "as0")
encoder.encodeInt32(Int32(self.selectionRange.upperBound), forKey: "as1")
}
}
public enum ChatTextInputStateTextAttributeType: PostboxCoding, Equatable {
case bold
case italic
case monospace
case textMention(PeerId)
case textUrl(String)
public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("t", orElse: 0) {
case 0:
self = .bold
case 1:
self = .italic
case 2:
self = .monospace
case 3:
self = .textMention(PeerId(decoder.decodeInt64ForKey("peerId", orElse: 0)))
case 4:
self = .textUrl(decoder.decodeStringForKey("url", orElse: ""))
default:
assertionFailure()
self = .bold
}
}
public func encode(_ encoder: PostboxEncoder) {
switch self {
case .bold:
encoder.encodeInt32(0, forKey: "t")
case .italic:
encoder.encodeInt32(1, forKey: "t")
case .monospace:
encoder.encodeInt32(2, forKey: "t")
case let .textMention(id):
encoder.encodeInt32(3, forKey: "t")
encoder.encodeInt64(id.toInt64(), forKey: "peerId")
case let .textUrl(url):
encoder.encodeInt32(4, forKey: "t")
encoder.encodeString(url, forKey: "url")
}
}
public static func ==(lhs: ChatTextInputStateTextAttributeType, rhs: ChatTextInputStateTextAttributeType) -> Bool {
switch lhs {
case .bold:
if case .bold = rhs {
return true
} else {
return false
}
case .italic:
if case .italic = rhs {
return true
} else {
return false
}
case .monospace:
if case .monospace = rhs {
return true
} else {
return false
}
case let .textMention(id):
if case .textMention(id) = rhs {
return true
} else {
return false
}
case let .textUrl(url):
if case .textUrl(url) = rhs {
return true
} else {
return false
}
}
}
}
public struct ChatTextInputStateTextAttribute: PostboxCoding, Equatable {
public let type: ChatTextInputStateTextAttributeType
public let range: Range<Int>
public init(type: ChatTextInputStateTextAttributeType, range: Range<Int>) {
self.type = type
self.range = range
}
public init(decoder: PostboxDecoder) {
self.type = decoder.decodeObjectForKey("type", decoder: { ChatTextInputStateTextAttributeType(decoder: $0) }) as! ChatTextInputStateTextAttributeType
self.range = Int(decoder.decodeInt32ForKey("range0", orElse: 0)) ..< Int(decoder.decodeInt32ForKey("range1", orElse: 0))
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeObject(self.type, forKey: "type")
encoder.encodeInt32(Int32(self.range.lowerBound), forKey: "range0")
encoder.encodeInt32(Int32(self.range.upperBound), forKey: "range1")
}
public static func ==(lhs: ChatTextInputStateTextAttribute, rhs: ChatTextInputStateTextAttribute) -> Bool {
return lhs.type == rhs.type && lhs.range == rhs.range
}
}
public struct ChatTextInputStateText: PostboxCoding, Equatable {
public let text: String
public let attributes: [ChatTextInputStateTextAttribute]
public init() {
self.text = ""
self.attributes = []
}
public init(text: String, attributes: [ChatTextInputStateTextAttribute]) {
self.text = text
self.attributes = attributes
}
public init(attributedText: NSAttributedString) {
self.text = attributedText.string
var parsedAttributes: [ChatTextInputStateTextAttribute] = []
attributedText.enumerateAttributes(in: NSRange(location: 0, length: attributedText.length), options: [], using: { attributes, range, _ in
for (key, value) in attributes {
if key == ChatTextInputAttributes.bold {
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .bold, range: range.location ..< (range.location + range.length)))
} else if key == ChatTextInputAttributes.italic {
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .italic, range: range.location ..< (range.location + range.length)))
} else if key == ChatTextInputAttributes.monospace {
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .monospace, range: range.location ..< (range.location + range.length)))
} else if key == ChatTextInputAttributes.textMention, let value = value as? ChatTextInputTextMentionAttribute {
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .textMention(value.peerId), range: range.location ..< (range.location + range.length)))
} else if key == ChatTextInputAttributes.textUrl, let value = value as? ChatTextInputTextUrlAttribute {
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .textUrl(value.url), range: range.location ..< (range.location + range.length)))
}
}
})
self.attributes = parsedAttributes
}
public init(decoder: PostboxDecoder) {
self.text = decoder.decodeStringForKey("text", orElse: "")
self.attributes = decoder.decodeObjectArrayWithDecoderForKey("attributes")
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeString(self.text, forKey: "text")
encoder.encodeObjectArray(self.attributes, forKey: "attributes")
}
static public func ==(lhs: ChatTextInputStateText, rhs: ChatTextInputStateText) -> Bool {
return lhs.text == rhs.text && lhs.attributes == rhs.attributes
}
public func attributedText() -> NSAttributedString {
let result = NSMutableAttributedString(string: self.text)
for attribute in self.attributes {
switch attribute.type {
case .bold:
result.addAttribute(ChatTextInputAttributes.bold, value: true as NSNumber, range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
case .italic:
result.addAttribute(ChatTextInputAttributes.italic, value: true as NSNumber, range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
case .monospace:
result.addAttribute(ChatTextInputAttributes.monospace, value: true as NSNumber, range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
case let .textMention(id):
result.addAttribute(ChatTextInputAttributes.textMention, value: ChatTextInputTextMentionAttribute(peerId: id), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
case let .textUrl(url):
result.addAttribute(ChatTextInputAttributes.textUrl, value: ChatTextInputTextUrlAttribute(url: url), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
}
}
return result
}
}
public enum ChatControllerSubject: Equatable {
case message(MessageId)
case scheduledMessages
}
public enum ChatControllerPresentationMode: Equatable {
case standard(previewing: Bool)
case overlay(NavigationController?)
case inline(NavigationController?)
}
public final class ChatEmbeddedInterfaceState: PeerChatListEmbeddedInterfaceState {
public let timestamp: Int32
public let text: NSAttributedString
public init(timestamp: Int32, text: NSAttributedString) {
self.timestamp = timestamp
self.text = text
}
public init(decoder: PostboxDecoder) {
self.timestamp = decoder.decodeInt32ForKey("d", orElse: 0)
self.text = ((decoder.decodeObjectForKey("at", decoder: { ChatTextInputStateText(decoder: $0) }) as? ChatTextInputStateText) ?? ChatTextInputStateText()).attributedText()
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.timestamp, forKey: "d")
encoder.encodeObject(ChatTextInputStateText(attributedText: self.text), forKey: "at")
}
public func isEqual(to: PeerChatListEmbeddedInterfaceState) -> Bool {
if let to = to as? ChatEmbeddedInterfaceState {
return self.timestamp == to.timestamp && self.text.isEqual(to: to.text)
} else {
return false
}
}
}
public enum ChatPresentationInputQueryResult: Equatable {
case stickers([FoundStickerItem])
case hashtags([String])
case mentions([Peer])
case commands([PeerCommand])
case emojis([(String, String)], NSRange)
case contextRequestResult(Peer?, ChatContextResultCollection?)
public static func ==(lhs: ChatPresentationInputQueryResult, rhs: ChatPresentationInputQueryResult) -> Bool {
switch lhs {
case let .stickers(lhsItems):
if case let .stickers(rhsItems) = rhs, lhsItems == rhsItems {
return true
} else {
return false
}
case let .hashtags(lhsResults):
if case let .hashtags(rhsResults) = rhs {
return lhsResults == rhsResults
} else {
return false
}
case let .mentions(lhsPeers):
if case let .mentions(rhsPeers) = rhs {
if lhsPeers.count != rhsPeers.count {
return false
} else {
for i in 0 ..< lhsPeers.count {
if !lhsPeers[i].isEqual(rhsPeers[i]) {
return false
}
}
return true
}
} else {
return false
}
case let .commands(lhsCommands):
if case let .commands(rhsCommands) = rhs {
if lhsCommands != rhsCommands {
return false
}
return true
} else {
return false
}
case let .emojis(lhsValue, lhsRange):
if case let .emojis(rhsValue, rhsRange) = rhs {
if lhsRange != rhsRange {
return false
}
if lhsValue.count != rhsValue.count {
return false
}
for i in 0 ..< lhsValue.count {
if lhsValue[i] != rhsValue[i] {
return false
}
}
return true
} else {
return false
}
case let .contextRequestResult(lhsPeer, lhsCollection):
if case let .contextRequestResult(rhsPeer, rhsCollection) = rhs {
if let lhsPeer = lhsPeer, let rhsPeer = rhsPeer {
if !lhsPeer.isEqual(rhsPeer) {
return false
}
} else if (lhsPeer != nil) != (rhsPeer != nil) {
return false
}
if lhsCollection != rhsCollection {
return false
}
return true
} else {
return false
}
}
}
}
public let ChatControllerCount = Atomic<Int32>(value: 0)
public protocol ChatController: ViewController {
var chatLocation: ChatLocation { get }
var canReadHistory: ValuePromise<Bool> { get }
var parentController: ViewController? { get set }
func updatePresentationMode(_ mode: ChatControllerPresentationMode)
func beginMessageSearch(_ query: String)
func displayPromoAnnouncement(text: String)
}
public protocol ChatMessagePrevewItemNode: class {
var forwardInfoReferenceNode: ASDisplayNode? { get }
}
public enum FileMediaResourcePlaybackStatus: Equatable {
case playing
case paused
}
public struct FileMediaResourceStatus: Equatable {
public var mediaStatus: FileMediaResourceMediaStatus
public var fetchStatus: MediaResourceStatus
public init(mediaStatus: FileMediaResourceMediaStatus, fetchStatus: MediaResourceStatus) {
self.mediaStatus = mediaStatus
self.fetchStatus = fetchStatus
}
}
public enum FileMediaResourceMediaStatus: Equatable {
case fetchStatus(MediaResourceStatus)
case playbackStatus(FileMediaResourcePlaybackStatus)
}