mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-04-08 06:07:52 +00:00
Fixes
fix localeWithStrings globally (#30)
Fix badge on zoomed devices. closes #9
Hide channel bottom panel closes #27
Another attempt to fix badge on some Zoomed devices
Force System Share sheet tg://sg/debug
fixes for device badge
New Crowdin updates (#34)
* New translations sglocalizable.strings (Chinese Traditional)
* New translations sglocalizable.strings (Chinese Simplified)
* New translations sglocalizable.strings (Chinese Traditional)
Fix input panel hidden on selection (#31)
* added if check for selectionState != nil
* same order of subnodes
Revert "Fix input panel hidden on selection (#31)"
This reverts commit e8a8bb1496.
Fix input panel for channels Closes #37
Quickly share links with system's share menu
force tabbar when editing
increase height for correct animation
New translations sglocalizable.strings (Ukrainian) (#38)
Hide Post Story button
Fix 10.15.1
Fix archive option for long-tap
Enable in-app Safari
Disable some unsupported purchases
disableDeleteChatSwipeOption + refactor restart alert
Hide bot in suggestions list
Fix merge v11.0
Fix exceptions for safari webview controller
New Crowdin updates (#47)
* New translations sglocalizable.strings (Romanian)
* New translations sglocalizable.strings (French)
* New translations sglocalizable.strings (Spanish)
* New translations sglocalizable.strings (Afrikaans)
* New translations sglocalizable.strings (Arabic)
* New translations sglocalizable.strings (Catalan)
* New translations sglocalizable.strings (Czech)
* New translations sglocalizable.strings (Danish)
* New translations sglocalizable.strings (German)
* New translations sglocalizable.strings (Greek)
* New translations sglocalizable.strings (Finnish)
* New translations sglocalizable.strings (Hebrew)
* New translations sglocalizable.strings (Hungarian)
* New translations sglocalizable.strings (Italian)
* New translations sglocalizable.strings (Japanese)
* New translations sglocalizable.strings (Korean)
* New translations sglocalizable.strings (Dutch)
* New translations sglocalizable.strings (Norwegian)
* New translations sglocalizable.strings (Polish)
* New translations sglocalizable.strings (Portuguese)
* New translations sglocalizable.strings (Serbian (Cyrillic))
* New translations sglocalizable.strings (Swedish)
* New translations sglocalizable.strings (Turkish)
* New translations sglocalizable.strings (Vietnamese)
* New translations sglocalizable.strings (Indonesian)
* New translations sglocalizable.strings (Hindi)
* New translations sglocalizable.strings (Uzbek)
New Crowdin updates (#49)
* New translations sglocalizable.strings (Arabic)
* New translations sglocalizable.strings (Arabic)
New translations sglocalizable.strings (Russian) (#51)
Call confirmation
WIP Settings search
Settings Search
Localize placeholder
Update AccountUtils.swift
mark mutual contact
Align back context action to left
New Crowdin updates (#54)
* New translations sglocalizable.strings (Chinese Simplified)
* New translations sglocalizable.strings (Chinese Traditional)
* New translations sglocalizable.strings (Ukrainian)
Independent Playground app for simulator
New translations sglocalizable.strings (Ukrainian) (#55)
Playground UIKit base and controllers
Inject SwiftUI view with overflow to AsyncDisplayKit
Launch Playgound project on simulator
Create .swiftformat
Move Playground to example
Update .swiftformat
Init SwiftUIViewController
wip
New translations sglocalizable.strings (Chinese Traditional) (#57)
Xcode 16 fixes
Fix
New translations sglocalizable.strings (Italian) (#59)
New translations sglocalizable.strings (Chinese Simplified) (#63)
Force disable CallKit integration due to missing NSE Entitlement
Fix merge
Fix whole chat translator
Sweetpad config
Bump version
11.3.1 fixes
Mutual contact placement fix
Disable Video PIP swipe
Update versions.json
Fix PIP crash
1220 lines
48 KiB
Swift
1220 lines
48 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import TelegramCore
|
|
import Postbox
|
|
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(EnginePeer.Id?)
|
|
}
|
|
|
|
public struct DisplayTranscribeButton: Equatable {
|
|
public let canBeDisplayed: Bool
|
|
public let displayForNotConsumed: Bool
|
|
public let providedByGroupBoost: Bool
|
|
|
|
public init(
|
|
canBeDisplayed: Bool,
|
|
displayForNotConsumed: Bool,
|
|
providedByGroupBoost: Bool
|
|
) {
|
|
self.canBeDisplayed = canBeDisplayed
|
|
self.displayForNotConsumed = displayForNotConsumed
|
|
self.providedByGroupBoost = providedByGroupBoost
|
|
}
|
|
}
|
|
|
|
public let translateToLanguageSG: String?
|
|
public let translationSettings: TranslationSettings?
|
|
public let automaticDownloadPeerType: MediaAutoDownloadPeerType
|
|
public let automaticDownloadPeerId: EnginePeer.Id?
|
|
public let automaticDownloadNetworkType: MediaAutoDownloadNetworkType
|
|
public let preferredStoryHighQuality: Bool
|
|
public let isRecentActions: Bool
|
|
public let subject: ChatControllerSubject?
|
|
public let contactsPeerIds: Set<EnginePeer.Id>
|
|
public let channelDiscussionGroup: ChannelDiscussionGroupStatus
|
|
public let animatedEmojiStickers: [String: [StickerPackItem]]
|
|
public let additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]]
|
|
public let forcedResourceStatus: FileMediaResourceStatus?
|
|
public let currentlyPlayingMessageId: EngineMessage.Index?
|
|
public let isCopyProtectionEnabled: Bool
|
|
public let availableReactions: AvailableReactions?
|
|
public let availableMessageEffects: AvailableMessageEffects?
|
|
public let savedMessageTags: SavedMessageTags?
|
|
public let defaultReaction: MessageReaction.Reaction?
|
|
public let isPremium: Bool
|
|
public let forceInlineReactions: Bool
|
|
public let alwaysDisplayTranscribeButton: DisplayTranscribeButton
|
|
public let accountPeer: EnginePeer?
|
|
public let topicAuthorId: EnginePeer.Id?
|
|
public let hasBots: Bool
|
|
public let translateToLanguage: String?
|
|
public let maxReadStoryId: Int32?
|
|
public let recommendedChannels: RecommendedChannels?
|
|
public let audioTranscriptionTrial: AudioTranscription.TrialState
|
|
public let chatThemes: [TelegramTheme]
|
|
public let deviceContactsNumbers: Set<String>
|
|
public let isStandalone: Bool
|
|
public let isInline: Bool
|
|
public let showSensitiveContent: Bool
|
|
|
|
public init(
|
|
translateToLanguageSG: String? = nil,
|
|
translationSettings: TranslationSettings? = nil,
|
|
automaticDownloadPeerType: MediaAutoDownloadPeerType,
|
|
automaticDownloadPeerId: EnginePeer.Id?,
|
|
automaticDownloadNetworkType: MediaAutoDownloadNetworkType,
|
|
preferredStoryHighQuality: Bool = false,
|
|
isRecentActions: Bool = false,
|
|
subject: ChatControllerSubject? = nil,
|
|
contactsPeerIds: Set<EnginePeer.Id> = Set(),
|
|
channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown,
|
|
animatedEmojiStickers: [String: [StickerPackItem]] = [:],
|
|
additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]] = [:],
|
|
forcedResourceStatus: FileMediaResourceStatus? = nil,
|
|
currentlyPlayingMessageId: EngineMessage.Index? = nil,
|
|
isCopyProtectionEnabled: Bool = false,
|
|
availableReactions: AvailableReactions?,
|
|
availableMessageEffects: AvailableMessageEffects?,
|
|
savedMessageTags: SavedMessageTags?,
|
|
defaultReaction: MessageReaction.Reaction?,
|
|
isPremium: Bool,
|
|
accountPeer: EnginePeer?,
|
|
forceInlineReactions: Bool = false,
|
|
alwaysDisplayTranscribeButton: DisplayTranscribeButton = DisplayTranscribeButton(canBeDisplayed: false, displayForNotConsumed: false, providedByGroupBoost: false),
|
|
topicAuthorId: EnginePeer.Id? = nil,
|
|
hasBots: Bool = false,
|
|
translateToLanguage: String? = nil,
|
|
maxReadStoryId: Int32? = nil,
|
|
recommendedChannels: RecommendedChannels? = nil,
|
|
audioTranscriptionTrial: AudioTranscription.TrialState = .defaultValue,
|
|
chatThemes: [TelegramTheme] = [],
|
|
deviceContactsNumbers: Set<String> = Set(),
|
|
isStandalone: Bool = false,
|
|
isInline: Bool = false,
|
|
showSensitiveContent: Bool = false
|
|
) {
|
|
self.translateToLanguageSG = translateToLanguageSG
|
|
self.translationSettings = translationSettings
|
|
self.automaticDownloadPeerType = automaticDownloadPeerType
|
|
self.automaticDownloadPeerId = automaticDownloadPeerId
|
|
self.automaticDownloadNetworkType = automaticDownloadNetworkType
|
|
self.preferredStoryHighQuality = preferredStoryHighQuality
|
|
self.isRecentActions = isRecentActions
|
|
self.subject = subject
|
|
self.contactsPeerIds = contactsPeerIds
|
|
self.channelDiscussionGroup = channelDiscussionGroup
|
|
self.animatedEmojiStickers = animatedEmojiStickers
|
|
self.additionalAnimatedEmojiStickers = additionalAnimatedEmojiStickers
|
|
self.forcedResourceStatus = forcedResourceStatus
|
|
self.currentlyPlayingMessageId = currentlyPlayingMessageId
|
|
self.isCopyProtectionEnabled = isCopyProtectionEnabled
|
|
self.availableReactions = availableReactions
|
|
self.availableMessageEffects = availableMessageEffects
|
|
self.savedMessageTags = savedMessageTags
|
|
self.defaultReaction = defaultReaction
|
|
self.isPremium = isPremium
|
|
self.accountPeer = accountPeer
|
|
self.forceInlineReactions = forceInlineReactions
|
|
self.topicAuthorId = topicAuthorId
|
|
self.alwaysDisplayTranscribeButton = alwaysDisplayTranscribeButton
|
|
self.hasBots = hasBots
|
|
self.translateToLanguage = translateToLanguage
|
|
self.maxReadStoryId = maxReadStoryId
|
|
self.recommendedChannels = recommendedChannels
|
|
self.audioTranscriptionTrial = audioTranscriptionTrial
|
|
self.chatThemes = chatThemes
|
|
self.deviceContactsNumbers = deviceContactsNumbers
|
|
self.isStandalone = isStandalone
|
|
self.isInline = isInline
|
|
self.showSensitiveContent = showSensitiveContent
|
|
}
|
|
|
|
public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool {
|
|
if lhs.automaticDownloadPeerType != rhs.automaticDownloadPeerType {
|
|
return false
|
|
}
|
|
if lhs.translateToLanguageSG != rhs.translateToLanguageSG {
|
|
return false
|
|
}
|
|
if lhs.translationSettings != rhs.translationSettings {
|
|
return false
|
|
}
|
|
if lhs.automaticDownloadPeerId != rhs.automaticDownloadPeerId {
|
|
return false
|
|
}
|
|
if lhs.automaticDownloadNetworkType != rhs.automaticDownloadNetworkType {
|
|
return false
|
|
}
|
|
if lhs.preferredStoryHighQuality != rhs.preferredStoryHighQuality {
|
|
return false
|
|
}
|
|
if lhs.isRecentActions != rhs.isRecentActions {
|
|
return false
|
|
}
|
|
if lhs.subject != rhs.subject {
|
|
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.additionalAnimatedEmojiStickers != rhs.additionalAnimatedEmojiStickers {
|
|
return false
|
|
}
|
|
if lhs.forcedResourceStatus != rhs.forcedResourceStatus {
|
|
return false
|
|
}
|
|
if lhs.currentlyPlayingMessageId != rhs.currentlyPlayingMessageId {
|
|
return false
|
|
}
|
|
if lhs.isCopyProtectionEnabled != rhs.isCopyProtectionEnabled {
|
|
return false
|
|
}
|
|
if lhs.availableReactions != rhs.availableReactions {
|
|
return false
|
|
}
|
|
if lhs.savedMessageTags != rhs.savedMessageTags {
|
|
return false
|
|
}
|
|
if lhs.isPremium != rhs.isPremium {
|
|
return false
|
|
}
|
|
if lhs.accountPeer != rhs.accountPeer {
|
|
return false
|
|
}
|
|
if lhs.forceInlineReactions != rhs.forceInlineReactions {
|
|
return false
|
|
}
|
|
if lhs.topicAuthorId != rhs.topicAuthorId {
|
|
return false
|
|
}
|
|
if lhs.alwaysDisplayTranscribeButton != rhs.alwaysDisplayTranscribeButton {
|
|
return false
|
|
}
|
|
if lhs.hasBots != rhs.hasBots {
|
|
return false
|
|
}
|
|
if lhs.translateToLanguage != rhs.translateToLanguage {
|
|
return false
|
|
}
|
|
if lhs.maxReadStoryId != rhs.maxReadStoryId {
|
|
return false
|
|
}
|
|
if lhs.recommendedChannels != rhs.recommendedChannels {
|
|
return false
|
|
}
|
|
if lhs.audioTranscriptionTrial != rhs.audioTranscriptionTrial {
|
|
return false
|
|
}
|
|
if lhs.chatThemes != rhs.chatThemes {
|
|
return false
|
|
}
|
|
if lhs.deviceContactsNumbers != rhs.deviceContactsNumbers {
|
|
return false
|
|
}
|
|
if lhs.isStandalone != rhs.isStandalone {
|
|
return false
|
|
}
|
|
if lhs.isInline != rhs.isInline {
|
|
return false
|
|
}
|
|
if lhs.showSensitiveContent != rhs.showSensitiveContent {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
public extension ChatMessageItemAssociatedData {
|
|
var isInPinnedListMode: Bool {
|
|
if case .pinnedMessages = self.subject {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum ChatControllerInteractionLongTapAction {
|
|
case url(String)
|
|
case phone(String)
|
|
case mention(String)
|
|
case peerMention(EnginePeer.Id, 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: EnginePeer.Id, 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 struct ChatControllerInitialAttachBotStart {
|
|
public let botId: EnginePeer.Id
|
|
public let payload: String?
|
|
public let justInstalled: Bool
|
|
|
|
public init(botId: EnginePeer.Id, payload: String?, justInstalled: Bool) {
|
|
self.botId = botId
|
|
self.payload = payload
|
|
self.justInstalled = justInstalled
|
|
}
|
|
}
|
|
|
|
public struct ChatControllerInitialBotAppStart {
|
|
public let botApp: BotApp?
|
|
public let payload: String?
|
|
public let justInstalled: Bool
|
|
public let mode: ResolvedStartAppMode
|
|
|
|
public init(botApp: BotApp?, payload: String?, justInstalled: Bool, mode: ResolvedStartAppMode) {
|
|
self.botApp = botApp
|
|
self.payload = payload
|
|
self.justInstalled = justInstalled
|
|
self.mode = mode
|
|
}
|
|
}
|
|
|
|
public enum ChatControllerInteractionNavigateToPeer {
|
|
public struct InfoParams {
|
|
public let switchToRecommendedChannels: Bool
|
|
public let ignoreInSavedMessages: Bool
|
|
|
|
public init(switchToRecommendedChannels: Bool = false, ignoreInSavedMessages: Bool = false) {
|
|
self.switchToRecommendedChannels = switchToRecommendedChannels
|
|
self.ignoreInSavedMessages = ignoreInSavedMessages
|
|
}
|
|
}
|
|
|
|
case `default`
|
|
case chat(textInputState: ChatTextInputState?, subject: ChatControllerSubject?, peekData: ChatPeekTimeout?)
|
|
case info(InfoParams?)
|
|
case withBotStartPayload(ChatControllerInitialBotStart)
|
|
case withAttachBot(ChatControllerInitialAttachBotStart)
|
|
case withBotApp(ChatControllerInitialBotAppStart)
|
|
}
|
|
|
|
public struct ChatInterfaceForwardOptionsState: Codable, Equatable {
|
|
public var hideNames: Bool
|
|
public var hideCaptions: Bool
|
|
public var unhideNamesOnCaptionChange: Bool
|
|
|
|
public static func ==(lhs: ChatInterfaceForwardOptionsState, rhs: ChatInterfaceForwardOptionsState) -> Bool {
|
|
return lhs.hideNames == rhs.hideNames && lhs.hideCaptions == rhs.hideCaptions && lhs.unhideNamesOnCaptionChange == rhs.unhideNamesOnCaptionChange
|
|
}
|
|
|
|
public init(hideNames: Bool, hideCaptions: Bool, unhideNamesOnCaptionChange: Bool) {
|
|
self.hideNames = hideNames
|
|
self.hideCaptions = hideCaptions
|
|
self.unhideNamesOnCaptionChange = unhideNamesOnCaptionChange
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
|
|
|
self.hideNames = (try? container.decodeIfPresent(Bool.self, forKey: "hn")) ?? false
|
|
self.hideCaptions = (try? container.decodeIfPresent(Bool.self, forKey: "hc")) ?? false
|
|
self.unhideNamesOnCaptionChange = false
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: StringCodingKey.self)
|
|
|
|
try container.encode(self.hideNames, forKey: "hn")
|
|
try container.encode(self.hideCaptions, forKey: "hc")
|
|
}
|
|
}
|
|
|
|
public struct ChatTextInputState: Codable, Equatable {
|
|
public var inputText: NSAttributedString
|
|
public var 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(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
|
self.inputText = ((try? container.decode(ChatTextInputStateText.self, forKey: "at")) ?? ChatTextInputStateText()).attributedText()
|
|
let rangeFrom = (try? container.decode(Int32.self, forKey: "as0")) ?? 0
|
|
let rangeTo = (try? container.decode(Int32.self, forKey: "as1")) ?? 0
|
|
if rangeFrom <= rangeTo {
|
|
self.selectionRange = Int(rangeFrom) ..< Int(rangeTo)
|
|
} else {
|
|
let length = self.inputText.length
|
|
self.selectionRange = length ..< length
|
|
}
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: StringCodingKey.self)
|
|
|
|
try container.encode(ChatTextInputStateText(attributedText: self.inputText), forKey: "at")
|
|
try container.encode(Int32(self.selectionRange.lowerBound), forKey: "as0")
|
|
try container.encode(Int32(self.selectionRange.upperBound), forKey: "as1")
|
|
}
|
|
}
|
|
|
|
public enum ChatTextInputStateTextAttributeType: Codable, Equatable {
|
|
case bold
|
|
case italic
|
|
case monospace
|
|
case textMention(EnginePeer.Id)
|
|
case textUrl(String)
|
|
case customEmoji(stickerPack: StickerPackReference?, fileId: Int64)
|
|
case strikethrough
|
|
case underline
|
|
case spoiler
|
|
case quote(isCollapsed: Bool)
|
|
case codeBlock(language: String?)
|
|
case collapsedQuote(text: ChatTextInputStateText)
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
|
|
|
switch (try? container.decode(Int32.self, forKey: "t")) ?? 0 {
|
|
case 0:
|
|
self = .bold
|
|
case 1:
|
|
self = .italic
|
|
case 2:
|
|
self = .monospace
|
|
case 3:
|
|
let peerId = (try? container.decode(Int64.self, forKey: "peerId")) ?? 0
|
|
self = .textMention(EnginePeer.Id(peerId))
|
|
case 4:
|
|
let url = (try? container.decode(String.self, forKey: "url")) ?? ""
|
|
self = .textUrl(url)
|
|
case 5:
|
|
let stickerPack = try container.decodeIfPresent(StickerPackReference.self, forKey: "s")
|
|
let fileId = try container.decode(Int64.self, forKey: "f")
|
|
self = .customEmoji(stickerPack: stickerPack, fileId: fileId)
|
|
case 6:
|
|
self = .strikethrough
|
|
case 7:
|
|
self = .underline
|
|
case 8:
|
|
self = .spoiler
|
|
case 9:
|
|
self = .quote(isCollapsed: try container.decodeIfPresent(Bool.self, forKey: "isCollapsed") ?? false)
|
|
case 10:
|
|
self = .codeBlock(language: try container.decodeIfPresent(String.self, forKey: "l"))
|
|
case 11:
|
|
self = .collapsedQuote(text: try container.decode(ChatTextInputStateText.self, forKey: "text"))
|
|
default:
|
|
assertionFailure()
|
|
self = .bold
|
|
}
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: StringCodingKey.self)
|
|
switch self {
|
|
case .bold:
|
|
try container.encode(0 as Int32, forKey: "t")
|
|
case .italic:
|
|
try container.encode(1 as Int32, forKey: "t")
|
|
case .monospace:
|
|
try container.encode(2 as Int32, forKey: "t")
|
|
case let .textMention(id):
|
|
try container.encode(3 as Int32, forKey: "t")
|
|
try container.encode(id.toInt64(), forKey: "peerId")
|
|
case let .textUrl(url):
|
|
try container.encode(4 as Int32, forKey: "t")
|
|
try container.encode(url, forKey: "url")
|
|
case let .customEmoji(stickerPack, fileId):
|
|
try container.encode(5 as Int32, forKey: "t")
|
|
try container.encodeIfPresent(stickerPack, forKey: "s")
|
|
try container.encode(fileId, forKey: "f")
|
|
case .strikethrough:
|
|
try container.encode(6 as Int32, forKey: "t")
|
|
case .underline:
|
|
try container.encode(7 as Int32, forKey: "t")
|
|
case .spoiler:
|
|
try container.encode(8 as Int32, forKey: "t")
|
|
case let .quote(isCollapsed):
|
|
try container.encode(9 as Int32, forKey: "t")
|
|
try container.encode(isCollapsed, forKey: "isCollapsed")
|
|
case let .codeBlock(language):
|
|
try container.encode(10 as Int32, forKey: "t")
|
|
try container.encodeIfPresent(language, forKey: "l")
|
|
case let .collapsedQuote(text):
|
|
try container.encode(11 as Int32, forKey: "t")
|
|
try container.encode(text, forKey: "text")
|
|
}
|
|
}
|
|
}
|
|
|
|
public struct ChatTextInputStateTextAttribute: Codable, 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(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
|
self.type = try container.decode(ChatTextInputStateTextAttributeType.self, forKey: "type")
|
|
let rangeFrom = (try? container.decode(Int32.self, forKey: "range0")) ?? 0
|
|
let rangeTo = (try? container.decode(Int32.self, forKey: "range1")) ?? 0
|
|
|
|
self.range = Int(rangeFrom) ..< Int(rangeTo)
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: StringCodingKey.self)
|
|
|
|
try container.encode(self.type, forKey: "type")
|
|
|
|
try container.encode(Int32(self.range.lowerBound), forKey: "range0")
|
|
try container.encode(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: Codable, 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)))
|
|
} else if key == ChatTextInputAttributes.customEmoji, let value = value as? ChatTextInputTextCustomEmojiAttribute {
|
|
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .customEmoji(stickerPack: nil, fileId: value.fileId), range: range.location ..< (range.location + range.length)))
|
|
} else if key == ChatTextInputAttributes.strikethrough {
|
|
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .strikethrough, range: range.location ..< (range.location + range.length)))
|
|
} else if key == ChatTextInputAttributes.underline {
|
|
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .underline, range: range.location ..< (range.location + range.length)))
|
|
} else if key == ChatTextInputAttributes.spoiler {
|
|
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .spoiler, range: range.location ..< (range.location + range.length)))
|
|
}
|
|
}
|
|
})
|
|
attributedText.enumerateAttribute(ChatTextInputAttributes.block, in: NSRange(location: 0, length: attributedText.length), options: [], using: { value, range, _ in
|
|
if let value = value as? ChatTextInputTextQuoteAttribute {
|
|
switch value.kind {
|
|
case .quote:
|
|
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .quote(isCollapsed: value.isCollapsed), range: range.location ..< (range.location + range.length)))
|
|
case let .code(language):
|
|
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .codeBlock(language: language), range: range.location ..< (range.location + range.length)))
|
|
}
|
|
}
|
|
})
|
|
attributedText.enumerateAttribute(ChatTextInputAttributes.collapsedBlock, in: NSRange(location: 0, length: attributedText.length), options: [], using: { value, range, _ in
|
|
if let value = value as? NSAttributedString {
|
|
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .collapsedQuote(text: ChatTextInputStateText(attributedText: value)), range: range.location ..< (range.location + range.length)))
|
|
}
|
|
})
|
|
self.attributes = parsedAttributes
|
|
}
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
|
self.text = (try? container.decode(String.self, forKey: "text")) ?? ""
|
|
self.attributes = (try? container.decode([ChatTextInputStateTextAttribute].self, forKey: "attributes")) ?? []
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: StringCodingKey.self)
|
|
try container.encode(self.text, forKey: "text")
|
|
try container.encode(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))
|
|
case let .customEmoji(_, fileId):
|
|
result.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: nil), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
|
case .strikethrough:
|
|
result.addAttribute(ChatTextInputAttributes.strikethrough, value: true as NSNumber, range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
|
case .underline:
|
|
result.addAttribute(ChatTextInputAttributes.underline, value: true as NSNumber, range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
|
case .spoiler:
|
|
result.addAttribute(ChatTextInputAttributes.spoiler, value: true as NSNumber, range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
|
case let .quote(isCollapsed):
|
|
result.addAttribute(ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .quote, isCollapsed: isCollapsed), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
|
case let .codeBlock(language):
|
|
result.addAttribute(ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .code(language: language), isCollapsed: false), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
|
case let .collapsedQuote(text):
|
|
result.addAttribute(ChatTextInputAttributes.collapsedBlock, value: text.attributedText(), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
}
|
|
|
|
public enum ChatControllerSubject: Equatable {
|
|
public enum MessageSubject: Equatable {
|
|
case id(EngineMessage.Id)
|
|
case timestamp(Int32)
|
|
}
|
|
|
|
public struct ForwardOptions: Equatable {
|
|
public var hideNames: Bool
|
|
public var hideCaptions: Bool
|
|
|
|
public init(hideNames: Bool, hideCaptions: Bool) {
|
|
self.hideNames = hideNames
|
|
self.hideCaptions = hideCaptions
|
|
}
|
|
}
|
|
|
|
public struct LinkOptions: Equatable {
|
|
public var messageText: String
|
|
public var messageEntities: [MessageTextEntity]
|
|
public var hasAlternativeLinks: Bool
|
|
public var replyMessageId: EngineMessage.Id?
|
|
public var replyQuote: String?
|
|
public var url: String
|
|
public var webpage: TelegramMediaWebpage
|
|
public var linkBelowText: Bool
|
|
public var largeMedia: Bool
|
|
|
|
public init(
|
|
messageText: String,
|
|
messageEntities: [MessageTextEntity],
|
|
hasAlternativeLinks: Bool,
|
|
replyMessageId: EngineMessage.Id?,
|
|
replyQuote: String?,
|
|
url: String,
|
|
webpage: TelegramMediaWebpage,
|
|
linkBelowText: Bool,
|
|
largeMedia: Bool
|
|
) {
|
|
self.messageText = messageText
|
|
self.messageEntities = messageEntities
|
|
self.hasAlternativeLinks = hasAlternativeLinks
|
|
self.replyMessageId = replyMessageId
|
|
self.replyQuote = replyQuote
|
|
self.url = url
|
|
self.webpage = webpage
|
|
self.linkBelowText = linkBelowText
|
|
self.largeMedia = largeMedia
|
|
}
|
|
}
|
|
|
|
public enum MessageOptionsInfo: Equatable {
|
|
public struct Quote: Equatable {
|
|
public let messageId: EngineMessage.Id
|
|
public let text: String
|
|
public let offset: Int?
|
|
|
|
public init(messageId: EngineMessage.Id, text: String, offset: Int?) {
|
|
self.messageId = messageId
|
|
self.text = text
|
|
self.offset = offset
|
|
}
|
|
}
|
|
|
|
public struct SelectionState: Equatable {
|
|
public var canQuote: Bool
|
|
public var quote: Quote?
|
|
|
|
public init(canQuote: Bool, quote: Quote?) {
|
|
self.canQuote = canQuote
|
|
self.quote = quote
|
|
}
|
|
}
|
|
|
|
public struct Reply: Equatable {
|
|
public var quote: Quote?
|
|
public var selectionState: Promise<SelectionState>
|
|
|
|
public init(quote: Quote?, selectionState: Promise<SelectionState>) {
|
|
self.quote = quote
|
|
self.selectionState = selectionState
|
|
}
|
|
|
|
public static func ==(lhs: Reply, rhs: Reply) -> Bool {
|
|
if lhs.quote != rhs.quote {
|
|
return false
|
|
}
|
|
if lhs.selectionState !== rhs.selectionState {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
public struct Forward: Equatable {
|
|
public var options: Signal<ForwardOptions, NoError>
|
|
|
|
public init(options: Signal<ForwardOptions, NoError>) {
|
|
self.options = options
|
|
}
|
|
|
|
public static func ==(lhs: Forward, rhs: Forward) -> Bool {
|
|
return true
|
|
}
|
|
}
|
|
|
|
public struct Link: Equatable {
|
|
public var options: Signal<LinkOptions, NoError>
|
|
public var isCentered: Bool
|
|
|
|
public init(options: Signal<LinkOptions, NoError>, isCentered: Bool) {
|
|
self.options = options
|
|
self.isCentered = isCentered
|
|
}
|
|
|
|
public static func ==(lhs: Link, rhs: Link) -> Bool {
|
|
return true
|
|
}
|
|
}
|
|
|
|
case reply(Reply)
|
|
case forward(Forward)
|
|
case link(Link)
|
|
}
|
|
|
|
public struct MessageHighlight: Equatable {
|
|
public struct Quote: Equatable {
|
|
public var string: String
|
|
public var offset: Int?
|
|
|
|
public init(string: String, offset: Int?) {
|
|
self.string = string
|
|
self.offset = offset
|
|
}
|
|
}
|
|
|
|
public var quote: Quote?
|
|
|
|
public init(quote: Quote? = nil) {
|
|
self.quote = quote
|
|
}
|
|
}
|
|
|
|
case message(id: MessageSubject, highlight: MessageHighlight?, timecode: Double?, setupReply: Bool)
|
|
case scheduledMessages
|
|
case pinnedMessages(id: EngineMessage.Id?)
|
|
case messageOptions(peerIds: [EnginePeer.Id], ids: [EngineMessage.Id], info: MessageOptionsInfo)
|
|
case customChatContents(contents: ChatCustomContentsProtocol)
|
|
|
|
public static func ==(lhs: ChatControllerSubject, rhs: ChatControllerSubject) -> Bool {
|
|
switch lhs {
|
|
case let .message(lhsId, lhsHighlight, lhsTimecode, lhsSetupReply):
|
|
if case let .message(rhsId, rhsHighlight, rhsTimecode, rhsSetupReply) = rhs, lhsId == rhsId && lhsHighlight == rhsHighlight && lhsTimecode == rhsTimecode && lhsSetupReply == rhsSetupReply {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case .scheduledMessages:
|
|
if case .scheduledMessages = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .pinnedMessages(id):
|
|
if case .pinnedMessages(id) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .messageOptions(lhsPeerIds, lhsIds, lhsInfo):
|
|
if case let .messageOptions(rhsPeerIds, rhsIds, rhsInfo) = rhs, lhsPeerIds == rhsPeerIds, lhsIds == rhsIds, lhsInfo == rhsInfo {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .customChatContents(lhsValue):
|
|
if case let .customChatContents(rhsValue) = rhs, lhsValue === rhsValue {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
public var isService: Bool {
|
|
switch self {
|
|
case .message:
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum ChatControllerPresentationMode: Equatable {
|
|
public enum StandardPresentation: Equatable {
|
|
case `default`
|
|
case previewing
|
|
case embedded(invertDirection: Bool)
|
|
}
|
|
|
|
case standard(StandardPresentation)
|
|
case overlay(NavigationController?)
|
|
case inline(NavigationController?)
|
|
}
|
|
|
|
public enum ChatInputTextCommand: Equatable {
|
|
case command(PeerCommand)
|
|
case shortcut(ShortcutMessageList.Item)
|
|
}
|
|
|
|
public struct ChatInputQueryCommandsResult: Equatable {
|
|
public var commands: [ChatInputTextCommand]
|
|
public var accountPeer: EnginePeer?
|
|
public var hasShortcuts: Bool
|
|
public var query: String
|
|
|
|
public init(commands: [ChatInputTextCommand], accountPeer: EnginePeer?, hasShortcuts: Bool, query: String) {
|
|
self.commands = commands
|
|
self.accountPeer = accountPeer
|
|
self.hasShortcuts = hasShortcuts
|
|
self.query = query
|
|
}
|
|
}
|
|
|
|
public enum ChatPresentationInputQueryResult: Equatable {
|
|
case stickers([FoundStickerItem])
|
|
case hashtags([String], String)
|
|
case mentions([EnginePeer])
|
|
case commands(ChatInputQueryCommandsResult)
|
|
case emojis([(String, TelegramMediaFile?, String)], NSRange)
|
|
case contextRequestResult(EnginePeer?, 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, lhsQuery):
|
|
if case let .hashtags(rhsResults, rhsQuery) = rhs {
|
|
return lhsResults == rhsResults && lhsQuery == rhsQuery
|
|
} else {
|
|
return false
|
|
}
|
|
case let .mentions(lhsPeers):
|
|
if case let .mentions(rhsPeers) = rhs {
|
|
if lhsPeers != rhsPeers {
|
|
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].0 != rhsValue[i].0 {
|
|
return false
|
|
}
|
|
if lhsValue[i].1?.fileId != rhsValue[i].1?.fileId {
|
|
return false
|
|
}
|
|
if lhsValue[i].2 != rhsValue[i].2 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .contextRequestResult(lhsPeer, lhsCollection):
|
|
if case let .contextRequestResult(rhsPeer, rhsCollection) = rhs {
|
|
if lhsPeer != rhsPeer {
|
|
return false
|
|
}
|
|
if lhsCollection != rhsCollection {
|
|
return false
|
|
}
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public let ChatControllerCount = Atomic<Int32>(value: 0)
|
|
|
|
public final class PeerInfoNavigationSourceTag {
|
|
public let peerId: EnginePeer.Id
|
|
|
|
public init(peerId: EnginePeer.Id) {
|
|
self.peerId = peerId
|
|
}
|
|
}
|
|
|
|
public protocol PeerInfoScreen: ViewController {
|
|
var peerId: PeerId { get }
|
|
var privacySettings: Promise<AccountPrivacySettings?> { get }
|
|
|
|
func openBirthdaySetup()
|
|
func tabBarItemContextAction(sourceView: UIView, gesture: ContextGesture?)
|
|
func toggleStorySelection(ids: [Int32], isSelected: Bool)
|
|
func togglePaneIsReordering(isReordering: Bool)
|
|
func cancelItemSelection()
|
|
}
|
|
|
|
public extension Peer {
|
|
func canSetupAutoremoveTimeout(accountPeerId: EnginePeer.Id) -> Bool {
|
|
if let _ = self as? TelegramSecretChat {
|
|
return false
|
|
} else if let group = self as? TelegramGroup {
|
|
if case .creator = group.role {
|
|
return true
|
|
} else if case let .admin(rights, _) = group.role {
|
|
if rights.rights.contains(.canDeleteMessages) {
|
|
return true
|
|
}
|
|
}
|
|
} else if let user = self as? TelegramUser {
|
|
if user.id != accountPeerId && user.botInfo == nil {
|
|
return true
|
|
}
|
|
} else if let channel = self as? TelegramChannel {
|
|
if channel.hasPermission(.deleteAllMessages) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
}
|
|
|
|
public struct ChatControllerCustomNavigationPanelNodeLayoutResult {
|
|
public var backgroundHeight: CGFloat
|
|
public var insetHeight: CGFloat
|
|
public var hitTestSlop: CGFloat
|
|
|
|
public init(backgroundHeight: CGFloat, insetHeight: CGFloat, hitTestSlop: CGFloat) {
|
|
self.backgroundHeight = backgroundHeight
|
|
self.insetHeight = insetHeight
|
|
self.hitTestSlop = hitTestSlop
|
|
}
|
|
}
|
|
|
|
public protocol ChatControllerCustomNavigationPanelNode: ASDisplayNode {
|
|
typealias LayoutResult = ChatControllerCustomNavigationPanelNodeLayoutResult
|
|
|
|
func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, chatController: ChatController) -> LayoutResult
|
|
}
|
|
|
|
public protocol ChatController: ViewController {
|
|
var overlayTitle: String? { get }
|
|
var chatLocation: ChatLocation { get }
|
|
var canReadHistory: ValuePromise<Bool> { get }
|
|
var parentController: ViewController? { get set }
|
|
var customNavigationController: NavigationController? { get set }
|
|
|
|
var dismissPreviewing: (() -> Void)? { get set }
|
|
var purposefulAction: (() -> Void)? { get set }
|
|
|
|
var stateUpdated: ((ContainedViewLayoutTransition) -> Void)? { get set }
|
|
var customDismissSearch: (() -> Void)? { get set }
|
|
|
|
var selectedMessageIds: Set<EngineMessage.Id>? { get }
|
|
var presentationInterfaceStateSignal: Signal<Any, NoError> { get }
|
|
|
|
var customNavigationBarContentNode: NavigationBarContentNode? { get }
|
|
var customNavigationPanelNode: ChatControllerCustomNavigationPanelNode? { get }
|
|
|
|
var visibleContextController: ViewController? { get }
|
|
|
|
var contentContainerNode: ASDisplayNode { get }
|
|
|
|
var searching: ValuePromise<Bool> { get }
|
|
var searchResultsCount: ValuePromise<Int32> { get }
|
|
var externalSearchResultsCount: Int32? { get set }
|
|
|
|
var alwaysShowSearchResultsAsList: Bool { get set }
|
|
var includeSavedPeersInSearchResults: Bool { get set }
|
|
var showListEmptyResults: Bool { get set }
|
|
func beginMessageSearch(_ query: String)
|
|
|
|
func updatePresentationMode(_ mode: ChatControllerPresentationMode)
|
|
func displayPromoAnnouncement(text: String)
|
|
|
|
func updatePushedTransition(_ fraction: CGFloat, transition: ContainedViewLayoutTransition)
|
|
|
|
func hintPlayNextOutgoingGift()
|
|
|
|
var isSendButtonVisible: Bool { get }
|
|
|
|
var isSelectingMessagesUpdated: ((Bool) -> Void)? { get set }
|
|
func cancelSelectingMessages()
|
|
func activateSearch(domain: ChatSearchDomain, query: String)
|
|
func activateInput(type: ChatControllerActivateInput)
|
|
func beginClearHistory(type: InteractiveHistoryClearingType)
|
|
|
|
func performScrollToTop() -> Bool
|
|
func transferScrollingVelocity(_ velocity: CGFloat)
|
|
func updateIsScrollingLockedAtTop(isScrollingLockedAtTop: Bool)
|
|
|
|
func playShakeAnimation()
|
|
|
|
func removeAd(opaqueId: Data)
|
|
}
|
|
|
|
public protocol ChatMessagePreviewItemNode: AnyObject {
|
|
var forwardInfoReferenceNode: ASDisplayNode? { get }
|
|
}
|
|
|
|
public enum FileMediaResourcePlaybackStatus: Equatable {
|
|
case playing
|
|
case paused
|
|
}
|
|
|
|
public struct FileMediaResourceStatus: Equatable {
|
|
public var mediaStatus: FileMediaResourceMediaStatus
|
|
public var fetchStatus: EngineMediaResource.FetchStatus
|
|
|
|
public init(mediaStatus: FileMediaResourceMediaStatus, fetchStatus: EngineMediaResource.FetchStatus) {
|
|
self.mediaStatus = mediaStatus
|
|
self.fetchStatus = fetchStatus
|
|
}
|
|
}
|
|
|
|
public enum FileMediaResourceMediaStatus: Equatable {
|
|
case fetchStatus(EngineMediaResource.FetchStatus)
|
|
case playbackStatus(FileMediaResourcePlaybackStatus)
|
|
}
|
|
|
|
public protocol ChatMessageItemNodeProtocol: ListViewItemNode {
|
|
func makeProgress() -> Promise<Bool>?
|
|
func targetReactionView(value: MessageReaction.Reaction) -> UIView?
|
|
func targetForStoryTransition(id: StoryId) -> UIView?
|
|
func contentFrame() -> CGRect
|
|
func matchesMessage(id: MessageId) -> Bool
|
|
func cancelInsertionAnimations()
|
|
func messages() -> [Message]
|
|
}
|
|
|
|
public final class ChatControllerNavigationData: CustomViewControllerNavigationData {
|
|
public let peerId: PeerId
|
|
public let threadId: Int64?
|
|
|
|
public init(peerId: PeerId, threadId: Int64?) {
|
|
self.peerId = peerId
|
|
self.threadId = threadId
|
|
}
|
|
|
|
public func combine(summary: CustomViewControllerNavigationDataSummary?) -> CustomViewControllerNavigationDataSummary? {
|
|
if let summary = summary as? ChatControllerNavigationDataSummary {
|
|
return summary.adding(peerNavigationItem: ChatNavigationStackItem(peerId: self.peerId, threadId: threadId))
|
|
} else {
|
|
return ChatControllerNavigationDataSummary(peerNavigationItems: [ChatNavigationStackItem(peerId: self.peerId, threadId: threadId)])
|
|
}
|
|
}
|
|
}
|
|
|
|
public final class ChatControllerNavigationDataSummary: CustomViewControllerNavigationDataSummary {
|
|
public let peerNavigationItems: [ChatNavigationStackItem]
|
|
|
|
public init(peerNavigationItems: [ChatNavigationStackItem]) {
|
|
self.peerNavigationItems = peerNavigationItems
|
|
}
|
|
|
|
public func adding(peerNavigationItem: ChatNavigationStackItem) -> ChatControllerNavigationDataSummary {
|
|
var peerNavigationItems = self.peerNavigationItems
|
|
if let index = peerNavigationItems.firstIndex(of: peerNavigationItem) {
|
|
peerNavigationItems.removeSubrange(0 ... index)
|
|
}
|
|
peerNavigationItems.insert(peerNavigationItem, at: 0)
|
|
return ChatControllerNavigationDataSummary(peerNavigationItems: peerNavigationItems)
|
|
}
|
|
}
|
|
|
|
public enum ChatHistoryListSource {
|
|
public struct Quote {
|
|
public var text: String
|
|
public var offset: Int?
|
|
|
|
public init(text: String, offset: Int?) {
|
|
self.text = text
|
|
self.offset = offset
|
|
}
|
|
}
|
|
|
|
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 ChatQuickReplyShortcutType {
|
|
case generic
|
|
case greeting
|
|
case away
|
|
}
|
|
|
|
public enum ChatCustomContentsKind: Equatable {
|
|
case quickReplyMessageInput(shortcut: String, shortcutType: ChatQuickReplyShortcutType)
|
|
case businessLinkSetup(link: TelegramBusinessChatLinks.Link)
|
|
case hashTagSearch(publicPosts: Bool)
|
|
}
|
|
|
|
public protocol ChatCustomContentsProtocol: AnyObject {
|
|
var kind: ChatCustomContentsKind { get }
|
|
var historyView: Signal<(MessageHistoryView, ViewUpdateType), NoError> { get }
|
|
var messageLimit: Int? { get }
|
|
|
|
func enqueueMessages(messages: [EnqueueMessage])
|
|
func deleteMessages(ids: [EngineMessage.Id])
|
|
func editMessage(id: EngineMessage.Id, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool)
|
|
|
|
func quickReplyUpdateShortcut(value: String)
|
|
func businessLinkUpdate(message: String, entities: [MessageTextEntity], title: String?)
|
|
|
|
func loadMore()
|
|
|
|
func hashtagSearchUpdate(query: String)
|
|
var hashtagSearchResultsUpdate: ((SearchMessagesResult, SearchMessagesState)) -> Void { get set }
|
|
}
|
|
|
|
public enum ChatHistoryListDisplayHeaders {
|
|
case none
|
|
case all
|
|
case allButLast
|
|
}
|
|
|
|
public enum ChatHistoryListMode: Equatable {
|
|
case bubbles
|
|
case list(search: Bool, reversed: Bool, reverseGroups: Bool, displayHeaders: ChatHistoryListDisplayHeaders, hintLinks: Bool, isGlobalSearch: Bool)
|
|
}
|
|
|
|
public protocol ChatControllerInteractionProtocol: AnyObject {
|
|
}
|
|
|
|
public enum ChatHistoryNodeHistoryState: Equatable {
|
|
case loading
|
|
case loaded(isEmpty: Bool, hasReachedLimits: Bool)
|
|
}
|
|
|
|
public protocol ChatHistoryListNode: ListView {
|
|
var historyState: ValuePromise<ChatHistoryNodeHistoryState> { get }
|
|
|
|
func scrollToEndOfHistory()
|
|
func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets)
|
|
func messageInCurrentHistoryView(_ id: MessageId) -> Message?
|
|
|
|
var contentPositionChanged: (ListViewVisibleContentOffset) -> Void { get set }
|
|
}
|