mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
[WIP] Quotes and link previews
This commit is contained in:
parent
137c3d9101
commit
af8474aca5
88 changed files with 2582 additions and 966 deletions
|
|
@ -531,6 +531,7 @@ public enum ChatControllerSubject: Equatable {
|
|||
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
|
||||
|
|
@ -541,6 +542,7 @@ public enum ChatControllerSubject: Equatable {
|
|||
public init(
|
||||
messageText: String,
|
||||
messageEntities: [MessageTextEntity],
|
||||
hasAlternativeLinks: Bool,
|
||||
replyMessageId: EngineMessage.Id?,
|
||||
replyQuote: String?,
|
||||
url: String,
|
||||
|
|
@ -550,6 +552,7 @@ public enum ChatControllerSubject: Equatable {
|
|||
) {
|
||||
self.messageText = messageText
|
||||
self.messageEntities = messageEntities
|
||||
self.hasAlternativeLinks = hasAlternativeLinks
|
||||
self.replyMessageId = replyMessageId
|
||||
self.replyQuote = replyQuote
|
||||
self.url = url
|
||||
|
|
|
|||
|
|
@ -7,11 +7,31 @@ public enum ChatHistoryInitialSearchLocation: Equatable {
|
|||
case id(MessageId)
|
||||
}
|
||||
|
||||
public struct MessageHistoryScrollToSubject: Equatable {
|
||||
public var index: MessageHistoryAnchorIndex
|
||||
public var quote: String?
|
||||
|
||||
public init(index: MessageHistoryAnchorIndex, quote: String?) {
|
||||
self.index = index
|
||||
self.quote = quote
|
||||
}
|
||||
}
|
||||
|
||||
public struct MessageHistoryInitialSearchSubject: Equatable {
|
||||
public var location: ChatHistoryInitialSearchLocation
|
||||
public var quote: String?
|
||||
|
||||
public init(location: ChatHistoryInitialSearchLocation, quote: String?) {
|
||||
self.location = location
|
||||
self.quote = quote
|
||||
}
|
||||
}
|
||||
|
||||
public enum ChatHistoryLocation: Equatable {
|
||||
case Initial(count: Int)
|
||||
case InitialSearch(location: ChatHistoryInitialSearchLocation, count: Int, highlight: Bool)
|
||||
case InitialSearch(subject: MessageHistoryInitialSearchSubject, count: Int, highlight: Bool)
|
||||
case Navigation(index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, count: Int, highlight: Bool)
|
||||
case Scroll(index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, sourceIndex: MessageHistoryAnchorIndex, scrollPosition: ListViewScrollPosition, animated: Bool, highlight: Bool)
|
||||
case Scroll(subject: MessageHistoryScrollToSubject, anchorIndex: MessageHistoryAnchorIndex, sourceIndex: MessageHistoryAnchorIndex, scrollPosition: ListViewScrollPosition, animated: Bool, highlight: Bool)
|
||||
}
|
||||
|
||||
public struct ChatHistoryLocationInput: Equatable {
|
||||
|
|
|
|||
|
|
@ -41,15 +41,15 @@ public struct ChatInterfaceSelectionState: Codable, Equatable {
|
|||
}
|
||||
|
||||
public struct ChatEditMessageState: Codable, Equatable {
|
||||
public let messageId: EngineMessage.Id
|
||||
public let inputState: ChatTextInputState
|
||||
public let disableUrlPreview: String?
|
||||
public let inputTextMaxLength: Int32?
|
||||
public var messageId: EngineMessage.Id
|
||||
public var inputState: ChatTextInputState
|
||||
public var disableUrlPreviews: [String]
|
||||
public var inputTextMaxLength: Int32?
|
||||
|
||||
public init(messageId: EngineMessage.Id, inputState: ChatTextInputState, disableUrlPreview: String?, inputTextMaxLength: Int32?) {
|
||||
public init(messageId: EngineMessage.Id, inputState: ChatTextInputState, disableUrlPreviews: [String], inputTextMaxLength: Int32?) {
|
||||
self.messageId = messageId
|
||||
self.inputState = inputState
|
||||
self.disableUrlPreview = disableUrlPreview
|
||||
self.disableUrlPreviews = disableUrlPreviews
|
||||
self.inputTextMaxLength = inputTextMaxLength
|
||||
}
|
||||
|
||||
|
|
@ -68,7 +68,15 @@ public struct ChatEditMessageState: Codable, Equatable {
|
|||
self.inputState = ChatTextInputState()
|
||||
}
|
||||
|
||||
self.disableUrlPreview = try? container.decodeIfPresent(String.self, forKey: "dup")
|
||||
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")
|
||||
}
|
||||
|
||||
|
|
@ -82,20 +90,20 @@ public struct ChatEditMessageState: Codable, Equatable {
|
|||
|
||||
try container.encode(self.inputState, forKey: "is")
|
||||
|
||||
try container.encodeIfPresent(self.disableUrlPreview, forKey: "dup")
|
||||
try container.encode(self.disableUrlPreviews, forKey: "dupl")
|
||||
try container.encodeIfPresent(self.inputTextMaxLength, forKey: "tl")
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChatEditMessageState, rhs: ChatEditMessageState) -> Bool {
|
||||
return lhs.messageId == rhs.messageId && lhs.inputState == rhs.inputState && lhs.disableUrlPreview == rhs.disableUrlPreview && lhs.inputTextMaxLength == rhs.inputTextMaxLength
|
||||
return lhs.messageId == rhs.messageId && lhs.inputState == rhs.inputState && lhs.disableUrlPreviews == rhs.disableUrlPreviews && lhs.inputTextMaxLength == rhs.inputTextMaxLength
|
||||
}
|
||||
|
||||
public func withUpdatedInputState(_ inputState: ChatTextInputState) -> ChatEditMessageState {
|
||||
return ChatEditMessageState(messageId: self.messageId, inputState: inputState, disableUrlPreview: self.disableUrlPreview, inputTextMaxLength: self.inputTextMaxLength)
|
||||
return ChatEditMessageState(messageId: self.messageId, inputState: inputState, disableUrlPreviews: self.disableUrlPreviews, inputTextMaxLength: self.inputTextMaxLength)
|
||||
}
|
||||
|
||||
public func withUpdatedDisableUrlPreview(_ disableUrlPreview: String?) -> ChatEditMessageState {
|
||||
return ChatEditMessageState(messageId: self.messageId, inputState: self.inputState, disableUrlPreview: disableUrlPreview, inputTextMaxLength: self.inputTextMaxLength)
|
||||
public func withUpdatedDisableUrlPreviews(_ disableUrlPreviews: [String]) -> ChatEditMessageState {
|
||||
return ChatEditMessageState(messageId: self.messageId, inputState: self.inputState, disableUrlPreviews: disableUrlPreviews, inputTextMaxLength: self.inputTextMaxLength)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -278,7 +286,7 @@ public final class ChatInterfaceState: Codable, Equatable {
|
|||
|
||||
public let timestamp: Int32
|
||||
public let composeInputState: ChatTextInputState
|
||||
public let composeDisableUrlPreview: String?
|
||||
public let composeDisableUrlPreviews: [String]
|
||||
public let replyMessageSubject: ReplyMessageSubject?
|
||||
public let forwardMessageIds: [EngineMessage.Id]?
|
||||
public let forwardOptionsState: ChatInterfaceForwardOptionsState?
|
||||
|
|
@ -326,7 +334,7 @@ public final class ChatInterfaceState: Codable, Equatable {
|
|||
public init() {
|
||||
self.timestamp = 0
|
||||
self.composeInputState = ChatTextInputState()
|
||||
self.composeDisableUrlPreview = nil
|
||||
self.composeDisableUrlPreviews = []
|
||||
self.replyMessageSubject = nil
|
||||
self.forwardMessageIds = nil
|
||||
self.forwardOptionsState = nil
|
||||
|
|
@ -339,10 +347,10 @@ public final class ChatInterfaceState: Codable, Equatable {
|
|||
self.inputLanguage = nil
|
||||
}
|
||||
|
||||
public init(timestamp: Int32, composeInputState: ChatTextInputState, composeDisableUrlPreview: String?, replyMessageSubject: ReplyMessageSubject?, forwardMessageIds: [EngineMessage.Id]?, forwardOptionsState: ChatInterfaceForwardOptionsState?, editMessage: ChatEditMessageState?, selectionState: ChatInterfaceSelectionState?, messageActionsState: ChatInterfaceMessageActionsState, historyScrollState: ChatInterfaceHistoryScrollState?, mediaRecordingMode: ChatTextInputMediaRecordingButtonMode, silentPosting: Bool, inputLanguage: String?) {
|
||||
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, silentPosting: Bool, inputLanguage: String?) {
|
||||
self.timestamp = timestamp
|
||||
self.composeInputState = composeInputState
|
||||
self.composeDisableUrlPreview = composeDisableUrlPreview
|
||||
self.composeDisableUrlPreviews = composeDisableUrlPreviews
|
||||
self.replyMessageSubject = replyMessageSubject
|
||||
self.forwardMessageIds = forwardMessageIds
|
||||
self.forwardOptionsState = forwardOptionsState
|
||||
|
|
@ -364,10 +372,13 @@ public final class ChatInterfaceState: Codable, Equatable {
|
|||
} else {
|
||||
self.composeInputState = ChatTextInputState()
|
||||
}
|
||||
if let composeDisableUrlPreview = try? container.decodeIfPresent(String.self, forKey: "dup") {
|
||||
self.composeDisableUrlPreview = composeDisableUrlPreview
|
||||
|
||||
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.composeDisableUrlPreview = nil
|
||||
self.composeDisableUrlPreviews = []
|
||||
}
|
||||
|
||||
if let replyMessageSubject = try? container.decodeIfPresent(ReplyMessageSubject.self, forKey: "replyMessageSubject") {
|
||||
|
|
@ -422,11 +433,8 @@ public final class ChatInterfaceState: Codable, Equatable {
|
|||
|
||||
try container.encode(self.timestamp, forKey: "ts")
|
||||
try container.encode(self.composeInputState, forKey: "is")
|
||||
if let composeDisableUrlPreview = self.composeDisableUrlPreview {
|
||||
try container.encode(composeDisableUrlPreview, forKey: "dup")
|
||||
} else {
|
||||
try container.encodeNil(forKey: "dup")
|
||||
}
|
||||
try container.encode(self.composeDisableUrlPreviews, forKey: "dup;")
|
||||
|
||||
if let replyMessageSubject = self.replyMessageSubject {
|
||||
try container.encode(replyMessageSubject, forKey: "replyMessageSubject")
|
||||
} else {
|
||||
|
|
@ -472,7 +480,7 @@ public final class ChatInterfaceState: Codable, Equatable {
|
|||
}
|
||||
|
||||
public static func ==(lhs: ChatInterfaceState, rhs: ChatInterfaceState) -> Bool {
|
||||
if lhs.composeDisableUrlPreview != rhs.composeDisableUrlPreview {
|
||||
if lhs.composeDisableUrlPreviews != rhs.composeDisableUrlPreviews {
|
||||
return false
|
||||
}
|
||||
if let lhsForwardMessageIds = lhs.forwardMessageIds, let rhsForwardMessageIds = rhs.forwardMessageIds {
|
||||
|
|
@ -506,11 +514,11 @@ public final class ChatInterfaceState: Codable, Equatable {
|
|||
public func withUpdatedComposeInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState {
|
||||
let updatedComposeInputState = inputState
|
||||
|
||||
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
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, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
}
|
||||
|
||||
public func withUpdatedComposeDisableUrlPreview(_ disableUrlPreview: String?) -> ChatInterfaceState {
|
||||
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: disableUrlPreview, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
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, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
}
|
||||
|
||||
public func withUpdatedEffectiveInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState {
|
||||
|
|
@ -522,19 +530,19 @@ public final class ChatInterfaceState: Codable, Equatable {
|
|||
updatedComposeInputState = inputState
|
||||
}
|
||||
|
||||
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: updatedEditMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
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, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
}
|
||||
|
||||
public func withUpdatedReplyMessageSubject(_ replyMessageSubject: ReplyMessageSubject?) -> ChatInterfaceState {
|
||||
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageSubject: replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
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, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
}
|
||||
|
||||
public func withUpdatedForwardMessageIds(_ forwardMessageIds: [EngineMessage.Id]?) -> ChatInterfaceState {
|
||||
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
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, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
}
|
||||
|
||||
public func withUpdatedForwardOptionsState(_ forwardOptionsState: ChatInterfaceForwardOptionsState?) -> ChatInterfaceState {
|
||||
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
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, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
}
|
||||
|
||||
public func withUpdatedSelectedMessages(_ messageIds: [EngineMessage.Id]) -> ChatInterfaceState {
|
||||
|
|
@ -545,7 +553,7 @@ public final class ChatInterfaceState: Codable, Equatable {
|
|||
for messageId in messageIds {
|
||||
selectedIds.insert(messageId)
|
||||
}
|
||||
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, 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, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
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, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
}
|
||||
|
||||
public func withToggledSelectedMessages(_ messageIds: [EngineMessage.Id], value: Bool) -> ChatInterfaceState {
|
||||
|
|
@ -560,39 +568,39 @@ public final class ChatInterfaceState: Codable, Equatable {
|
|||
selectedIds.remove(messageId)
|
||||
}
|
||||
}
|
||||
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, 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, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
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, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
}
|
||||
|
||||
public func withoutSelectionState() -> ChatInterfaceState {
|
||||
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: nil, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
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, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
}
|
||||
|
||||
public func withUpdatedTimestamp(_ timestamp: Int32) -> ChatInterfaceState {
|
||||
return ChatInterfaceState(timestamp: timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
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, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
}
|
||||
|
||||
public func withUpdatedEditMessage(_ editMessage: ChatEditMessageState?) -> ChatInterfaceState {
|
||||
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
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, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
}
|
||||
|
||||
public func withUpdatedMessageActionsState(_ f: (ChatInterfaceMessageActionsState) -> ChatInterfaceMessageActionsState) -> ChatInterfaceState {
|
||||
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, 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, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
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, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
}
|
||||
|
||||
public func withUpdatedHistoryScrollState(_ historyScrollState: ChatInterfaceHistoryScrollState?) -> ChatInterfaceState {
|
||||
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
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, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
}
|
||||
|
||||
public func withUpdatedMediaRecordingMode(_ mediaRecordingMode: ChatTextInputMediaRecordingButtonMode) -> ChatInterfaceState {
|
||||
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
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, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
|
||||
}
|
||||
|
||||
public func withUpdatedSilentPosting(_ silentPosting: Bool) -> ChatInterfaceState {
|
||||
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: silentPosting, inputLanguage: self.inputLanguage)
|
||||
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, silentPosting: silentPosting, inputLanguage: self.inputLanguage)
|
||||
}
|
||||
|
||||
public func withUpdatedInputLanguage(_ inputLanguage: String?) -> ChatInterfaceState {
|
||||
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: inputLanguage)
|
||||
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, silentPosting: self.silentPosting, inputLanguage: inputLanguage)
|
||||
}
|
||||
|
||||
public static func parse(_ state: OpaqueChatInterfaceState) -> ChatInterfaceState {
|
||||
|
|
|
|||
|
|
@ -3014,7 +3014,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
strongSelf.interaction.dismissInput()
|
||||
strongSelf.interaction.present(controller, nil)
|
||||
} else if case let .messages(chatLocation, _, _) = playlistLocation {
|
||||
let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60, highlight: true), id: 0), context: strongSelf.context, chatLocation: chatLocation, subject: nil, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), tagMask: EngineMessage.Tags.music)
|
||||
let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(id.messageId), quote: nil), count: 60, highlight: true), id: 0), context: strongSelf.context, chatLocation: chatLocation, subject: nil, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), tagMask: EngineMessage.Tags.music)
|
||||
|
||||
var cancelImpl: (() -> Void)?
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
|
@ -3603,7 +3603,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
|
|||
return nil
|
||||
case .links:
|
||||
var media: [EngineMedia] = []
|
||||
media.append(.webpage(TelegramMediaWebpage(webpageId: EngineMedia.Id(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: "https://telegram.org", displayUrl: "https://telegram.org", hash: 0, type: nil, websiteName: "Telegram", title: "Telegram Telegram", text: "Telegram", embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, image: nil, file: nil, story: nil, attributes: [], instantPage: nil, displayOptions: .default)))))
|
||||
media.append(.webpage(TelegramMediaWebpage(webpageId: EngineMedia.Id(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: "https://telegram.org", displayUrl: "https://telegram.org", hash: 0, type: nil, websiteName: "Telegram", title: "Telegram Telegram", text: "Telegram", embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, isMediaLargeByDefault: nil, image: nil, file: nil, story: nil, attributes: [], instantPage: nil)))))
|
||||
let message = EngineMessage(
|
||||
stableId: 0,
|
||||
stableVersion: 0,
|
||||
|
|
|
|||
|
|
@ -307,6 +307,10 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
|
|||
}
|
||||
case _ as TelegramMediaGiveaway:
|
||||
messageText = strings.Message_Giveaway
|
||||
case let webpage as TelegramMediaWebpage:
|
||||
if messageText.isEmpty, case let .Loaded(content) = webpage.content {
|
||||
messageText = content.displayUrl
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -324,6 +324,20 @@ public final class ChatPresentationInterfaceState: Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
public struct UrlPreview: Equatable {
|
||||
public var url: String
|
||||
public var webPage: TelegramMediaWebpage
|
||||
public var positionBelowText: Bool
|
||||
public var largeMedia: Bool?
|
||||
|
||||
public init(url: String, webPage: TelegramMediaWebpage, positionBelowText: Bool, largeMedia: Bool?) {
|
||||
self.url = url
|
||||
self.webPage = webPage
|
||||
self.positionBelowText = positionBelowText
|
||||
self.largeMedia = largeMedia
|
||||
}
|
||||
}
|
||||
|
||||
public let interfaceState: ChatInterfaceState
|
||||
public let chatLocation: ChatLocation
|
||||
public let renderedPeer: RenderedPeer?
|
||||
|
|
@ -350,8 +364,8 @@ public final class ChatPresentationInterfaceState: Equatable {
|
|||
public let slowmodeState: ChatSlowmodeState?
|
||||
public let chatHistoryState: ChatHistoryNodeHistoryState?
|
||||
public let botStartPayload: String?
|
||||
public let urlPreview: (String, TelegramMediaWebpage)?
|
||||
public let editingUrlPreview: (String, TelegramMediaWebpage)?
|
||||
public let urlPreview: UrlPreview?
|
||||
public let editingUrlPreview: UrlPreview?
|
||||
public let search: ChatSearchData?
|
||||
public let searchQuerySuggestionResult: ChatPresentationInputQueryResult?
|
||||
public let presentationReady: Bool
|
||||
|
|
@ -466,7 +480,7 @@ public final class ChatPresentationInterfaceState: Equatable {
|
|||
self.translationState = nil
|
||||
}
|
||||
|
||||
public init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isNotAccessible: Bool, explicitelyCanPinMessages: Bool, contactStatus: ChatContactStatus?, hasBots: Bool, isArchived: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, recordedMediaPreview: ChatRecordedMediaPreview?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: ChatPinnedMessage?, peerIsBlocked: Bool, peerIsMuted: Bool, peerDiscussionId: PeerId?, peerGeoLocation: PeerGeoLocation?, callsAvailable: Bool, callsPrivate: Bool, slowmodeState: ChatSlowmodeState?, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: (String, TelegramMediaWebpage)?, editingUrlPreview: (String, TelegramMediaWebpage)?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, presentationReady: Bool, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, hasScheduledMessages: Bool, autoremoveTimeout: Int32?, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, hasActiveGroupCall: Bool, importState: ChatPresentationImportState?, reportReason: ReportReason?, showCommands: Bool, hasBotCommands: Bool, showSendAsPeers: Bool, sendAsPeers: [SendAsPeer]?, botMenuButton: BotMenuButton, showWebView: Bool, currentSendAsPeerId: PeerId?, copyProtectionEnabled: Bool, hasPlentyOfMessages: Bool, isPremium: Bool, premiumGiftOptions: [CachedPremiumGiftOption], suggestPremiumGift: Bool, forceInputCommandsHidden: Bool, voiceMessagesAvailable: Bool, customEmojiAvailable: Bool, threadData: ThreadData?, isGeneralThreadClosed: Bool?, translationState: ChatPresentationTranslationState?) {
|
||||
public init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isNotAccessible: Bool, explicitelyCanPinMessages: Bool, contactStatus: ChatContactStatus?, hasBots: Bool, isArchived: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, recordedMediaPreview: ChatRecordedMediaPreview?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: ChatPinnedMessage?, peerIsBlocked: Bool, peerIsMuted: Bool, peerDiscussionId: PeerId?, peerGeoLocation: PeerGeoLocation?, callsAvailable: Bool, callsPrivate: Bool, slowmodeState: ChatSlowmodeState?, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: UrlPreview?, editingUrlPreview: UrlPreview?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, presentationReady: Bool, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, hasScheduledMessages: Bool, autoremoveTimeout: Int32?, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, hasActiveGroupCall: Bool, importState: ChatPresentationImportState?, reportReason: ReportReason?, showCommands: Bool, hasBotCommands: Bool, showSendAsPeers: Bool, sendAsPeers: [SendAsPeer]?, botMenuButton: BotMenuButton, showWebView: Bool, currentSendAsPeerId: PeerId?, copyProtectionEnabled: Bool, hasPlentyOfMessages: Bool, isPremium: Bool, premiumGiftOptions: [CachedPremiumGiftOption], suggestPremiumGift: Bool, forceInputCommandsHidden: Bool, voiceMessagesAvailable: Bool, customEmojiAvailable: Bool, threadData: ThreadData?, isGeneralThreadClosed: Bool?, translationState: ChatPresentationTranslationState?) {
|
||||
self.interfaceState = interfaceState
|
||||
self.chatLocation = chatLocation
|
||||
self.renderedPeer = renderedPeer
|
||||
|
|
@ -622,20 +636,14 @@ public final class ChatPresentationInterfaceState: Equatable {
|
|||
return false
|
||||
}
|
||||
if let lhsUrlPreview = lhs.urlPreview, let rhsUrlPreview = rhs.urlPreview {
|
||||
if lhsUrlPreview.0 != rhsUrlPreview.0 {
|
||||
return false
|
||||
}
|
||||
if !lhsUrlPreview.1.isEqual(to: rhsUrlPreview.1) {
|
||||
if lhsUrlPreview != rhsUrlPreview {
|
||||
return false
|
||||
}
|
||||
} else if (lhs.urlPreview != nil) != (rhs.urlPreview != nil) {
|
||||
return false
|
||||
}
|
||||
if let lhsEditingUrlPreview = lhs.editingUrlPreview, let rhsEditingUrlPreview = rhs.editingUrlPreview {
|
||||
if lhsEditingUrlPreview.0 != rhsEditingUrlPreview.0 {
|
||||
return false
|
||||
}
|
||||
if !lhsEditingUrlPreview.1.isEqual(to: rhsEditingUrlPreview.1) {
|
||||
if lhsEditingUrlPreview != rhsEditingUrlPreview {
|
||||
return false
|
||||
}
|
||||
} else if (lhs.editingUrlPreview != nil) != (rhs.editingUrlPreview != nil) {
|
||||
|
|
@ -869,11 +877,11 @@ public final class ChatPresentationInterfaceState: Equatable {
|
|||
return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState)
|
||||
}
|
||||
|
||||
public func updatedUrlPreview(_ urlPreview: (String, TelegramMediaWebpage)?) -> ChatPresentationInterfaceState {
|
||||
public func updatedUrlPreview(_ urlPreview: UrlPreview?) -> ChatPresentationInterfaceState {
|
||||
return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState)
|
||||
}
|
||||
|
||||
public func updatedEditingUrlPreview(_ editingUrlPreview: (String, TelegramMediaWebpage)?) -> ChatPresentationInterfaceState {
|
||||
public func updatedEditingUrlPreview(_ editingUrlPreview: UrlPreview?) -> ChatPresentationInterfaceState {
|
||||
return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, importState: self.importState, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2226,6 +2226,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
|||
case custom(ContextControllerItemsContent)
|
||||
}
|
||||
|
||||
public var id: AnyHashable?
|
||||
public var content: Content
|
||||
public var context: AccountContext?
|
||||
public var reactionItems: [ReactionContextItem]
|
||||
|
|
@ -2238,6 +2239,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
|||
public var dismissed: (() -> Void)?
|
||||
|
||||
public init(
|
||||
id: AnyHashable? = nil,
|
||||
content: Content,
|
||||
context: AccountContext? = nil,
|
||||
reactionItems: [ReactionContextItem] = [],
|
||||
|
|
@ -2249,6 +2251,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
|||
tipSignal: Signal<Tip?, NoError>? = nil,
|
||||
dismissed: (() -> Void)? = nil
|
||||
) {
|
||||
self.id = id
|
||||
self.content = content
|
||||
self.context = context
|
||||
self.animationCache = animationCache
|
||||
|
|
@ -2262,6 +2265,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
|||
}
|
||||
|
||||
public init() {
|
||||
self.id = nil
|
||||
self.content = .list([])
|
||||
self.context = nil
|
||||
self.reactionItems = []
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ public protocol ContextControllerActionsStackItem: AnyObject {
|
|||
requestUpdateApparentHeight: @escaping (ContainedViewLayoutTransition) -> Void
|
||||
) -> ContextControllerActionsStackItemNode
|
||||
|
||||
var id: AnyHashable? { get }
|
||||
var tip: ContextController.Tip? { get }
|
||||
var tipSignal: Signal<ContextController.Tip?, NoError>? { get }
|
||||
var reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)? { get }
|
||||
|
|
@ -833,6 +834,7 @@ final class ContextControllerActionsListStackItem: ContextControllerActionsStack
|
|||
}
|
||||
}
|
||||
|
||||
let id: AnyHashable?
|
||||
private let items: [ContextMenuItem]
|
||||
let reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?
|
||||
let tip: ContextController.Tip?
|
||||
|
|
@ -840,12 +842,14 @@ final class ContextControllerActionsListStackItem: ContextControllerActionsStack
|
|||
let dismissed: (() -> Void)?
|
||||
|
||||
init(
|
||||
id: AnyHashable?,
|
||||
items: [ContextMenuItem],
|
||||
reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?,
|
||||
tip: ContextController.Tip?,
|
||||
tipSignal: Signal<ContextController.Tip?, NoError>?,
|
||||
dismissed: (() -> Void)?
|
||||
) {
|
||||
self.id = id
|
||||
self.items = items
|
||||
self.reactionItems = reactionItems
|
||||
self.tip = tip
|
||||
|
|
@ -928,6 +932,7 @@ final class ContextControllerActionsCustomStackItem: ContextControllerActionsSta
|
|||
}
|
||||
}
|
||||
|
||||
let id: AnyHashable?
|
||||
private let content: ContextControllerItemsContent
|
||||
let reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?
|
||||
let tip: ContextController.Tip?
|
||||
|
|
@ -935,12 +940,14 @@ final class ContextControllerActionsCustomStackItem: ContextControllerActionsSta
|
|||
let dismissed: (() -> Void)?
|
||||
|
||||
init(
|
||||
id: AnyHashable?,
|
||||
content: ContextControllerItemsContent,
|
||||
reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?,
|
||||
tip: ContextController.Tip?,
|
||||
tipSignal: Signal<ContextController.Tip?, NoError>?,
|
||||
dismissed: (() -> Void)?
|
||||
) {
|
||||
self.id = id
|
||||
self.content = content
|
||||
self.reactionItems = reactionItems
|
||||
self.tip = tip
|
||||
|
|
@ -970,11 +977,11 @@ func makeContextControllerActionsStackItem(items: ContextController.Items) -> [C
|
|||
}
|
||||
switch items.content {
|
||||
case let .list(listItems):
|
||||
return [ContextControllerActionsListStackItem(items: listItems, reactionItems: reactionItems, tip: items.tip, tipSignal: items.tipSignal, dismissed: items.dismissed)]
|
||||
return [ContextControllerActionsListStackItem(id: items.id, items: listItems, reactionItems: reactionItems, tip: items.tip, tipSignal: items.tipSignal, dismissed: items.dismissed)]
|
||||
case let .twoLists(listItems1, listItems2):
|
||||
return [ContextControllerActionsListStackItem(items: listItems1, reactionItems: nil, tip: nil, tipSignal: nil, dismissed: items.dismissed), ContextControllerActionsListStackItem(items: listItems2, reactionItems: nil, tip: nil, tipSignal: nil, dismissed: nil)]
|
||||
return [ContextControllerActionsListStackItem(id: items.id, items: listItems1, reactionItems: nil, tip: nil, tipSignal: nil, dismissed: items.dismissed), ContextControllerActionsListStackItem(id: nil, items: listItems2, reactionItems: nil, tip: nil, tipSignal: nil, dismissed: nil)]
|
||||
case let .custom(customContent):
|
||||
return [ContextControllerActionsCustomStackItem(content: customContent, reactionItems: reactionItems, tip: items.tip, tipSignal: items.tipSignal, dismissed: items.dismissed)]
|
||||
return [ContextControllerActionsCustomStackItem(id: items.id, content: customContent, reactionItems: reactionItems, tip: items.tip, tipSignal: items.tipSignal, dismissed: items.dismissed)]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1084,6 +1091,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
|
|||
final class ItemContainer: ASDisplayNode {
|
||||
let getController: () -> ContextControllerProtocol?
|
||||
let requestUpdate: (ContainedViewLayoutTransition) -> Void
|
||||
let item: ContextControllerActionsStackItem
|
||||
let node: ContextControllerActionsStackItemNode
|
||||
let dimNode: ASDisplayNode
|
||||
var tip: ContextController.Tip?
|
||||
|
|
@ -1110,6 +1118,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
|
|||
) {
|
||||
self.getController = getController
|
||||
self.requestUpdate = requestUpdate
|
||||
self.item = item
|
||||
self.node = item.node(
|
||||
getController: getController,
|
||||
requestDismiss: requestDismiss,
|
||||
|
|
@ -1316,9 +1325,20 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
|
||||
func replace(item: ContextControllerActionsStackItem, animated: Bool) {
|
||||
func replace(item: ContextControllerActionsStackItem, animated: Bool?) {
|
||||
var resolvedAnimated = false
|
||||
if let animated {
|
||||
resolvedAnimated = animated
|
||||
} else {
|
||||
if let id = item.id, let lastId = self.itemContainers.last?.item.id {
|
||||
if id != lastId {
|
||||
resolvedAnimated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for itemContainer in self.itemContainers {
|
||||
if animated {
|
||||
if resolvedAnimated {
|
||||
self.dismissingItemContainers.append((itemContainer, false))
|
||||
} else {
|
||||
itemContainer.tipNode?.removeFromSupernode()
|
||||
|
|
@ -1328,7 +1348,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
|
|||
self.itemContainers.removeAll()
|
||||
self.navigationContainer.isNavigationEnabled = self.itemContainers.count > 1
|
||||
|
||||
self.push(item: item, currentScrollingState: nil, positionLock: nil, animated: animated)
|
||||
self.push(item: item, currentScrollingState: nil, positionLock: nil, animated: resolvedAnimated)
|
||||
}
|
||||
|
||||
func push(item: ContextControllerActionsStackItem, currentScrollingState: CGFloat?, positionLock: CGFloat?, animated: Bool) {
|
||||
|
|
|
|||
|
|
@ -484,7 +484,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||
}
|
||||
}
|
||||
|
||||
func replaceItems(items: ContextController.Items, animated: Bool) {
|
||||
func replaceItems(items: ContextController.Items, animated: Bool?) {
|
||||
if case .twoLists = items.content {
|
||||
let stackItems = makeContextControllerActionsStackItem(items: items)
|
||||
self.actionsStackNode.replace(item: stackItems.first!, animated: animated)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ enum ContextControllerPresentationNodeStateTransition {
|
|||
protocol ContextControllerPresentationNode: ASDisplayNode {
|
||||
var ready: Signal<Bool, NoError> { get }
|
||||
|
||||
func replaceItems(items: ContextController.Items, animated: Bool)
|
||||
func replaceItems(items: ContextController.Items, animated: Bool?)
|
||||
func pushItems(items: ContextController.Items)
|
||||
func popItems()
|
||||
func wantsDisplayBelowKeyboard() -> Bool
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ final class ContextSourceContainer: ASDisplayNode {
|
|||
return
|
||||
}
|
||||
|
||||
self.setItems(items: items, animated: false)
|
||||
self.setItems(items: items, animated: nil)
|
||||
self.actionsReady.set(.single(true))
|
||||
}))
|
||||
}
|
||||
|
|
@ -273,7 +273,7 @@ final class ContextSourceContainer: ASDisplayNode {
|
|||
}))
|
||||
}
|
||||
|
||||
func setItems(items: ContextController.Items, animated: Bool) {
|
||||
func setItems(items: ContextController.Items, animated: Bool?) {
|
||||
self.presentationNode.replaceItems(items: items, animated: animated)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ private func drawConnectingCorner(context: CGContext, color: UIColor, at point:
|
|||
}
|
||||
}
|
||||
|
||||
public func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat, outerRadius: CGFloat, innerRadius: CGFloat, stroke: Bool = false, useModernPathCalculation: Bool) -> (CGPoint, UIImage?) {
|
||||
public func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat, outerRadius: CGFloat, innerRadius: CGFloat, stroke: Bool = false, strokeWidth: CGFloat = 2.0, useModernPathCalculation: Bool) -> (CGPoint, UIImage?) {
|
||||
if rects.isEmpty {
|
||||
return (CGPoint(), nil)
|
||||
}
|
||||
|
|
@ -66,10 +66,15 @@ public func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat,
|
|||
bottomRight.y = max(bottomRight.y, rects[i].maxY)
|
||||
}
|
||||
|
||||
topLeft.x -= inset
|
||||
topLeft.y -= inset
|
||||
bottomRight.x += inset * 2.0
|
||||
bottomRight.y += inset * 2.0
|
||||
var drawingInset = inset
|
||||
if stroke {
|
||||
drawingInset += 2.0
|
||||
}
|
||||
|
||||
topLeft.x -= drawingInset
|
||||
topLeft.y -= drawingInset
|
||||
bottomRight.x += drawingInset * 2.0
|
||||
bottomRight.y += drawingInset * 2.0
|
||||
|
||||
return (topLeft, generateImage(CGSize(width: bottomRight.x - topLeft.x, height: bottomRight.y - topLeft.y), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
|
@ -84,7 +89,7 @@ public func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat,
|
|||
|
||||
if stroke {
|
||||
context.setStrokeColor(color.cgColor)
|
||||
context.setLineWidth(2.0)
|
||||
context.setLineWidth(strokeWidth)
|
||||
context.strokePath()
|
||||
} else {
|
||||
context.fillPath()
|
||||
|
|
@ -212,7 +217,7 @@ public func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat,
|
|||
|
||||
if stroke {
|
||||
context.setStrokeColor(color.cgColor)
|
||||
context.setLineWidth(2.0)
|
||||
context.setLineWidth(strokeWidth)
|
||||
context.strokePath()
|
||||
} else {
|
||||
context.fillPath()
|
||||
|
|
@ -306,6 +311,8 @@ public final class LinkHighlightingNode: ASDisplayNode {
|
|||
public var outerRadius: CGFloat = 4.0
|
||||
public var inset: CGFloat = 2.0
|
||||
public var useModernPathCalculation: Bool = false
|
||||
public var borderOnly: Bool = false
|
||||
public var strokeWidth: CGFloat = 1.0
|
||||
|
||||
private var _color: UIColor
|
||||
public var color: UIColor {
|
||||
|
|
@ -352,7 +359,7 @@ public final class LinkHighlightingNode: ASDisplayNode {
|
|||
if self.rects.isEmpty {
|
||||
self.imageNode.image = nil
|
||||
}
|
||||
let (offset, image) = generateRectsImage(color: self.color, rects: self.rects, inset: self.inset, outerRadius: self.outerRadius, innerRadius: self.innerRadius, useModernPathCalculation: self.useModernPathCalculation)
|
||||
let (offset, image) = generateRectsImage(color: self.color, rects: self.rects, inset: self.inset, outerRadius: self.outerRadius, innerRadius: self.innerRadius, stroke: self.borderOnly, strokeWidth: self.strokeWidth, useModernPathCalculation: self.useModernPathCalculation)
|
||||
|
||||
if let image = image {
|
||||
self.imageNode.image = image
|
||||
|
|
|
|||
|
|
@ -453,7 +453,7 @@ public final class TextNodeLayout: NSObject {
|
|||
|
||||
for blockQuote in self.blockQuotes {
|
||||
if lastLine.frame.intersects(blockQuote.frame) {
|
||||
width = max(width, blockQuote.frame.maxX)
|
||||
width = max(width, ceil(blockQuote.frame.maxX) + 2.0)
|
||||
}
|
||||
}
|
||||
return width
|
||||
|
|
@ -764,6 +764,17 @@ public final class TextNodeLayout: NSObject {
|
|||
return nil
|
||||
}
|
||||
|
||||
public func attributeSubstringWithRange(name: String, index: Int) -> (String, String, NSRange)? {
|
||||
if let attributedString = self.attributedString {
|
||||
var range = NSRange()
|
||||
let _ = attributedString.attribute(NSAttributedString.Key(rawValue: name), at: index, effectiveRange: &range)
|
||||
if range.length != 0 {
|
||||
return ((attributedString.string as NSString).substring(with: range), attributedString.string, range)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func allAttributeRects(name: String) -> [(Any, CGRect)] {
|
||||
guard let attributedString = self.attributedString else {
|
||||
return []
|
||||
|
|
@ -1064,6 +1075,10 @@ open class TextNode: ASDisplayNode {
|
|||
return self.cachedLayout?.attributeSubstring(name: name, index: index)
|
||||
}
|
||||
|
||||
public func attributeSubstringWithRange(name: String, index: Int) -> (String, String, NSRange)? {
|
||||
return self.cachedLayout?.attributeSubstringWithRange(name: name, index: index)
|
||||
}
|
||||
|
||||
public func attributeRects(name: String, at index: Int) -> [CGRect]? {
|
||||
if let cachedLayout = self.cachedLayout {
|
||||
return cachedLayout.lineAndAttributeRects(name: name, at: index)?.map { $0.1 }
|
||||
|
|
@ -1341,6 +1356,9 @@ open class TextNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
|
||||
size.width = ceil(size.width)
|
||||
size.height = ceil(size.height)
|
||||
|
||||
let rawTextSize = size
|
||||
size.width += insets.left + insets.right
|
||||
size.height += insets.top + insets.bottom
|
||||
|
|
@ -1992,7 +2010,8 @@ open class TextNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
for blockQuote in layout.blockQuotes {
|
||||
let radius: CGFloat = 3.0
|
||||
let radius: CGFloat = 4.0
|
||||
let lineWidth: CGFloat = 3.0
|
||||
|
||||
var blockFrame = blockQuote.frame.offsetBy(dx: offset.x + 2.0, dy: offset.y)
|
||||
blockFrame.size.width += 4.0
|
||||
|
|
@ -2014,13 +2033,15 @@ open class TextNode: ASDisplayNode {
|
|||
context.restoreGState()
|
||||
context.resetClip()
|
||||
|
||||
let lineFrame = CGRect(origin: CGPoint(x: blockFrame.minX, y: blockFrame.minY), size: CGSize(width: radius, height: blockFrame.height))
|
||||
let lineFrame = CGRect(origin: CGPoint(x: blockFrame.minX, y: blockFrame.minY), size: CGSize(width: lineWidth, height: blockFrame.height))
|
||||
context.move(to: CGPoint(x: lineFrame.minX, y: lineFrame.minY + radius))
|
||||
context.addArc(tangent1End: CGPoint(x: lineFrame.minX, y: lineFrame.minY), tangent2End: CGPoint(x: lineFrame.minX + radius, y: lineFrame.minY), radius: radius)
|
||||
context.addLine(to: CGPoint(x: lineFrame.minX + radius, y: lineFrame.maxY))
|
||||
context.addArc(tangent1End: CGPoint(x: lineFrame.minX, y: lineFrame.maxY), tangent2End: CGPoint(x: lineFrame.minX, y: lineFrame.maxY - radius), radius: radius)
|
||||
context.closePath()
|
||||
context.fillPath()
|
||||
context.clip()
|
||||
context.fill(lineFrame)
|
||||
context.resetClip()
|
||||
}
|
||||
|
||||
if let textShadowColor = layout.textShadowColor {
|
||||
|
|
|
|||
|
|
@ -632,7 +632,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation:
|
|||
let frame = CGRect(origin: CGPoint(x: floor((boundingWidth - size.width) / 2.0), y: 0.0), size: size)
|
||||
let item: InstantPageItem
|
||||
if let url = url, let coverId = coverId, case let .image(image) = media[coverId] {
|
||||
let loadedContent = TelegramMediaWebpageLoadedContent(url: url, displayUrl: url, hash: 0, type: "video", websiteName: nil, title: nil, text: nil, embedUrl: url, embedType: "video", embedSize: PixelDimensions(size), duration: nil, author: nil, image: image, file: nil, story: nil, attributes: [], instantPage: nil, displayOptions: .default)
|
||||
let loadedContent = TelegramMediaWebpageLoadedContent(url: url, displayUrl: url, hash: 0, type: "video", websiteName: nil, title: nil, text: nil, embedUrl: url, embedType: "video", embedSize: PixelDimensions(size), duration: nil, author: nil, isMediaLargeByDefault: nil, image: image, file: nil, story: nil, attributes: [], instantPage: nil)
|
||||
let content = TelegramMediaWebpageContent.Loaded(loadedContent)
|
||||
|
||||
item = InstantPageImageItem(frame: frame, webPage: webpage, media: InstantPageMedia(index: embedIndex, media: .webpage(TelegramMediaWebpage(webpageId: EngineMedia.Id(namespace: Namespaces.Media.LocalWebpage, id: -1), content: content)), url: nil, caption: nil, credit: nil), attributes: [], interactive: true, roundCorners: false, fit: false)
|
||||
|
|
|
|||
|
|
@ -818,7 +818,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||
strongSelf.displayNode.view.window?.endEditing(true)
|
||||
strongSelf.present(controller, in: .window(.root))
|
||||
} else if case let .messages(chatLocation, _, _) = playlistLocation {
|
||||
let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60, highlight: true), id: 0), context: strongSelf.context, chatLocation: chatLocation, subject: nil, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), tagMask: MessageTags.music)
|
||||
let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(id.messageId), quote: nil), count: 60, highlight: true), id: 0), context: strongSelf.context, chatLocation: chatLocation, subject: nil, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), tagMask: MessageTags.music)
|
||||
|
||||
var cancelImpl: (() -> Void)?
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
|
|
|||
|
|
@ -261,8 +261,9 @@ func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: Refere
|
|||
let peerId: PeerId = chatPeerId.peerId
|
||||
|
||||
switch replyTo {
|
||||
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, _, replyToTopId, quoteText, quoteEntities):
|
||||
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, replyMedia, replyToTopId, quoteText, quoteEntities):
|
||||
let _ = replyHeader
|
||||
let _ = replyMedia
|
||||
let _ = replyToTopId
|
||||
let _ = quoteText
|
||||
let _ = quoteEntities
|
||||
|
|
@ -282,8 +283,9 @@ func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: Refere
|
|||
case let .messageService(_, id, _, chatPeerId, replyHeader, _, _, _):
|
||||
if let replyHeader = replyHeader {
|
||||
switch replyHeader {
|
||||
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, _, replyToTopId, quoteText, quoteEntities):
|
||||
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, replyMedia, replyToTopId, quoteText, quoteEntities):
|
||||
let _ = replyHeader
|
||||
let _ = replyMedia
|
||||
let _ = replyToTopId
|
||||
let _ = quoteText
|
||||
let _ = quoteEntities
|
||||
|
|
@ -302,48 +304,63 @@ func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: Refere
|
|||
return nil
|
||||
}
|
||||
|
||||
func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerId: PeerId) -> (Media?, Int32?, Bool?, Bool?) {
|
||||
struct ParsedMessageWebpageAttributes {
|
||||
var forceLargeMedia: Bool?
|
||||
var isManuallyAdded: Bool
|
||||
}
|
||||
|
||||
func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerId: PeerId) -> (media: Media?, expirationTimer: Int32?, nonPremium: Bool?, hasSpoiler: Bool?, webpageAttributes: ParsedMessageWebpageAttributes?) {
|
||||
if let media = media {
|
||||
switch media {
|
||||
case let .messageMediaPhoto(flags, photo, ttlSeconds):
|
||||
if let photo = photo {
|
||||
if let mediaImage = telegramMediaImageFromApiPhoto(photo) {
|
||||
return (mediaImage, ttlSeconds, nil, (flags & (1 << 3)) != 0)
|
||||
return (mediaImage, ttlSeconds, nil, (flags & (1 << 3)) != 0, nil)
|
||||
}
|
||||
} else {
|
||||
return (TelegramMediaExpiredContent(data: .image), nil, nil, nil)
|
||||
return (TelegramMediaExpiredContent(data: .image), nil, nil, nil, nil)
|
||||
}
|
||||
case let .messageMediaContact(phoneNumber, firstName, lastName, vcard, userId):
|
||||
let contactPeerId: PeerId? = userId == 0 ? nil : PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||
let mediaContact = TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: contactPeerId, vCardData: vcard.isEmpty ? nil : vcard)
|
||||
return (mediaContact, nil, nil, nil)
|
||||
return (mediaContact, nil, nil, nil, nil)
|
||||
case let .messageMediaGeo(geo):
|
||||
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil, heading: nil)
|
||||
return (mediaMap, nil, nil, nil)
|
||||
return (mediaMap, nil, nil, nil, nil)
|
||||
case let .messageMediaVenue(geo, title, address, provider, venueId, venueType):
|
||||
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: title, address: address, provider: provider, venueId: venueId, venueType: venueType, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil, heading: nil)
|
||||
return (mediaMap, nil, nil, nil)
|
||||
return (mediaMap, nil, nil, nil, nil)
|
||||
case let .messageMediaGeoLive(_, geo, heading, period, proximityNotificationRadius):
|
||||
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: period, liveProximityNotificationRadius: proximityNotificationRadius, heading: heading)
|
||||
return (mediaMap, nil, nil, nil)
|
||||
return (mediaMap, nil, nil, nil, nil)
|
||||
case let .messageMediaDocument(flags, document, _, ttlSeconds):
|
||||
if let document = document {
|
||||
if let mediaFile = telegramMediaFileFromApiDocument(document) {
|
||||
return (mediaFile, ttlSeconds, (flags & (1 << 3)) != 0, (flags & (1 << 4)) != 0)
|
||||
return (mediaFile, ttlSeconds, (flags & (1 << 3)) != 0, (flags & (1 << 4)) != 0, nil)
|
||||
}
|
||||
} else {
|
||||
return (TelegramMediaExpiredContent(data: .file), nil, nil, nil)
|
||||
return (TelegramMediaExpiredContent(data: .file), nil, nil, nil, nil)
|
||||
}
|
||||
case let .messageMediaWebPage(_, webpage):
|
||||
case let .messageMediaWebPage(flags, webpage):
|
||||
if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage, url: nil) {
|
||||
return (mediaWebpage, nil, nil, nil)
|
||||
var webpageForceLargeMedia: Bool?
|
||||
if (flags & (1 << 0)) != 0 {
|
||||
webpageForceLargeMedia = true
|
||||
} else if (flags & (1 << 1)) != 0 {
|
||||
webpageForceLargeMedia = false
|
||||
}
|
||||
|
||||
return (mediaWebpage, nil, nil, nil, ParsedMessageWebpageAttributes(
|
||||
forceLargeMedia: webpageForceLargeMedia,
|
||||
isManuallyAdded: (flags & (1 << 3)) != 0
|
||||
))
|
||||
}
|
||||
case .messageMediaUnsupported:
|
||||
return (TelegramMediaUnsupported(), nil, nil, nil)
|
||||
return (TelegramMediaUnsupported(), nil, nil, nil, nil)
|
||||
case .messageMediaEmpty:
|
||||
break
|
||||
case let .messageMediaGame(game):
|
||||
return (TelegramMediaGame(apiGame: game), nil, nil, nil)
|
||||
return (TelegramMediaGame(apiGame: game), nil, nil, nil, nil)
|
||||
case let .messageMediaInvoice(flags, title, description, photo, receiptMsgId, currency, totalAmount, startParam, apiExtendedMedia):
|
||||
var parsedFlags = TelegramMediaInvoiceFlags()
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
|
|
@ -367,7 +384,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
|
|||
}
|
||||
extendedMedia = .preview(dimensions: dimensions, immediateThumbnailData: immediateThumbnailData, videoDuration: videoDuration)
|
||||
case let .messageExtendedMedia(apiMedia):
|
||||
let (media, _, _, _) = textMediaAndExpirationTimerFromApiMedia(apiMedia, peerId)
|
||||
let (media, _, _, _, _) = textMediaAndExpirationTimerFromApiMedia(apiMedia, peerId)
|
||||
if let media = media {
|
||||
extendedMedia = .full(media: media)
|
||||
} else {
|
||||
|
|
@ -378,7 +395,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
|
|||
extendedMedia = nil
|
||||
}
|
||||
|
||||
return (TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: receiptMsgId.flatMap { MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }, currency: currency, totalAmount: totalAmount, startParam: startParam, extendedMedia: extendedMedia, flags: parsedFlags, version: TelegramMediaInvoice.lastVersion), nil, nil, nil)
|
||||
return (TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: receiptMsgId.flatMap { MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }, currency: currency, totalAmount: totalAmount, startParam: startParam, extendedMedia: extendedMedia, flags: parsedFlags, version: TelegramMediaInvoice.lastVersion), nil, nil, nil, nil)
|
||||
case let .messageMediaPoll(poll, results):
|
||||
switch poll {
|
||||
case let .poll(id, flags, question, answers, closePeriod, _):
|
||||
|
|
@ -394,23 +411,23 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
|
|||
} else {
|
||||
kind = .poll(multipleAnswers: (flags & (1 << 2)) != 0)
|
||||
}
|
||||
return (TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: question, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod), nil, nil, nil)
|
||||
return (TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: question, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod), nil, nil, nil, nil)
|
||||
}
|
||||
case let .messageMediaDice(value, emoticon):
|
||||
return (TelegramMediaDice(emoji: emoticon, value: value), nil, nil, nil)
|
||||
return (TelegramMediaDice(emoji: emoticon, value: value), nil, nil, nil, nil)
|
||||
case let .messageMediaStory(flags, peerId, id, _):
|
||||
let isMention = (flags & (1 << 1)) != 0
|
||||
return (TelegramMediaStory(storyId: StoryId(peerId: peerId.peerId, id: id), isMention: isMention), nil, nil, nil)
|
||||
return (TelegramMediaStory(storyId: StoryId(peerId: peerId.peerId, id: id), isMention: isMention), nil, nil, nil, nil)
|
||||
case let .messageMediaGiveaway(apiFlags, channels, countries, quantity, months, untilDate):
|
||||
var flags: TelegramMediaGiveaway.Flags = []
|
||||
if (apiFlags & (1 << 0)) != 0 {
|
||||
flags.insert(.onlyNewSubscribers)
|
||||
}
|
||||
return (TelegramMediaGiveaway(flags: flags, channelPeerIds: channels.map { PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value($0)) }, countries: countries ?? [], quantity: quantity, months: months, untilDate: untilDate), nil, nil, nil)
|
||||
return (TelegramMediaGiveaway(flags: flags, channelPeerIds: channels.map { PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value($0)) }, countries: countries ?? [], quantity: quantity, months: months, untilDate: untilDate), nil, nil, nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
return (nil, nil, nil, nil)
|
||||
return (nil, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
func mediaAreaFromApiMediaArea(_ mediaArea: Api.MediaArea) -> MediaArea? {
|
||||
|
|
@ -567,12 +584,12 @@ extension StoreMessage {
|
|||
if let replyTo = replyTo {
|
||||
var threadMessageId: MessageId?
|
||||
switch replyTo {
|
||||
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, _, replyToTopId, quoteText, quoteEntities):
|
||||
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, replyMedia, replyToTopId, quoteText, quoteEntities):
|
||||
let isForumTopic = (flags & (1 << 3)) != 0
|
||||
|
||||
var quote: EngineMessageReplyQuote?
|
||||
if let quoteText = quoteText {
|
||||
quote = EngineMessageReplyQuote(text: quoteText, entities: messageTextEntitiesFromApiEntities(quoteEntities ?? []))
|
||||
if quoteText != nil || replyMedia != nil {
|
||||
quote = EngineMessageReplyQuote(text: quoteText ?? "", entities: messageTextEntitiesFromApiEntities(quoteEntities ?? []), media: textMediaAndExpirationTimerFromApiMedia(replyMedia, peerId).media)
|
||||
}
|
||||
|
||||
if let replyToMsgId = replyToMsgId {
|
||||
|
|
@ -669,7 +686,7 @@ extension StoreMessage {
|
|||
var consumableContent: (Bool, Bool)? = nil
|
||||
|
||||
if let media = media {
|
||||
let (mediaValue, expirationTimer, nonPremium, hasSpoiler) = textMediaAndExpirationTimerFromApiMedia(media, peerId)
|
||||
let (mediaValue, expirationTimer, nonPremium, hasSpoiler, webpageAttributes) = textMediaAndExpirationTimerFromApiMedia(media, peerId)
|
||||
if let mediaValue = mediaValue {
|
||||
medias.append(mediaValue)
|
||||
|
||||
|
|
@ -685,6 +702,14 @@ extension StoreMessage {
|
|||
if let hasSpoiler = hasSpoiler, hasSpoiler {
|
||||
attributes.append(MediaSpoilerMessageAttribute())
|
||||
}
|
||||
|
||||
if mediaValue is TelegramMediaWebpage {
|
||||
let leadingPreview = (flags & (1 << 27)) != 0
|
||||
|
||||
if let webpageAttributes {
|
||||
attributes.append(WebpagePreviewMessageAttribute(leadingPreview: leadingPreview, forceLargeMedia: webpageAttributes.forceLargeMedia, isManuallyAdded: webpageAttributes.isManuallyAdded))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -841,10 +866,10 @@ extension StoreMessage {
|
|||
if let replyTo = replyTo {
|
||||
var threadMessageId: MessageId?
|
||||
switch replyTo {
|
||||
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, _, replyToTopId, quoteText, quoteEntities):
|
||||
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, replyMedia, replyToTopId, quoteText, quoteEntities):
|
||||
var quote: EngineMessageReplyQuote?
|
||||
if let quoteText = quoteText {
|
||||
quote = EngineMessageReplyQuote(text: quoteText, entities: messageTextEntitiesFromApiEntities(quoteEntities ?? []))
|
||||
if quoteText != nil || replyMedia != nil {
|
||||
quote = EngineMessageReplyQuote(text: quoteText ?? "", entities: messageTextEntitiesFromApiEntities(quoteEntities ?? []), media: textMediaAndExpirationTimerFromApiMedia(replyMedia, peerId).media)
|
||||
}
|
||||
|
||||
if let replyToMsgId = replyToMsgId {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage, url: String?) ->
|
|||
case let .webPagePending(flags, id, url, date):
|
||||
let _ = flags
|
||||
return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Pending(date, url))
|
||||
case let .webPage(_, id, url, displayUrl, hash, type, siteName, title, description, photo, embedUrl, embedType, embedWidth, embedHeight, duration, author, document, cachedPage, attributes):
|
||||
case let .webPage(flags, id, url, displayUrl, hash, type, siteName, title, description, photo, embedUrl, embedType, embedWidth, embedHeight, duration, author, document, cachedPage, attributes):
|
||||
var embedSize: PixelDimensions?
|
||||
if let embedWidth = embedWidth, let embedHeight = embedHeight {
|
||||
embedSize = PixelDimensions(width: embedWidth, height: embedHeight)
|
||||
|
|
@ -56,7 +56,10 @@ func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage, url: String?) ->
|
|||
if let cachedPage = cachedPage {
|
||||
instantPage = InstantPage(apiPage: cachedPage)
|
||||
}
|
||||
return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Loaded(TelegramMediaWebpageLoadedContent(url: url, displayUrl: displayUrl, hash: hash, type: type, websiteName: siteName, title: title, text: description, embedUrl: embedUrl, embedType: embedType, embedSize: embedSize, duration: webpageDuration, author: author, image: image, file: file, story: story, attributes: webpageAttributes, instantPage: instantPage, displayOptions: .default)))
|
||||
|
||||
let isMediaLargeByDefault = (flags & (1 << 13)) != 0
|
||||
|
||||
return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Loaded(TelegramMediaWebpageLoadedContent(url: url, displayUrl: displayUrl, hash: hash, type: type, websiteName: siteName, title: title, text: description, embedUrl: embedUrl, embedType: embedType, embedSize: embedSize, duration: webpageDuration, author: author, isMediaLargeByDefault: isMediaLargeByDefault, image: image, file: file, story: story, attributes: webpageAttributes, instantPage: instantPage)))
|
||||
case .webPageEmpty:
|
||||
return nil
|
||||
}
|
||||
|
|
@ -66,16 +69,42 @@ public class WebpagePreviewMessageAttribute: MessageAttribute, Equatable {
|
|||
public let associatedPeerIds: [PeerId] = []
|
||||
public let associatedMediaIds: [MediaId] = []
|
||||
|
||||
public init() {
|
||||
public let leadingPreview: Bool
|
||||
public let forceLargeMedia: Bool?
|
||||
public let isManuallyAdded: Bool
|
||||
|
||||
public init(leadingPreview: Bool, forceLargeMedia: Bool?, isManuallyAdded: Bool) {
|
||||
self.leadingPreview = leadingPreview
|
||||
self.forceLargeMedia = forceLargeMedia
|
||||
self.isManuallyAdded = isManuallyAdded
|
||||
}
|
||||
|
||||
required public init(decoder: PostboxDecoder) {
|
||||
self.leadingPreview = decoder.decodeBoolForKey("lp", orElse: false)
|
||||
self.forceLargeMedia = decoder.decodeOptionalBoolForKey("lm")
|
||||
self.isManuallyAdded = decoder.decodeBoolForKey("ma", orElse: false)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeBool(self.leadingPreview, forKey: "lp")
|
||||
if let forceLargeMedia = self.forceLargeMedia {
|
||||
encoder.encodeBool(forceLargeMedia, forKey: "lm")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "lm")
|
||||
}
|
||||
encoder.encodeBool(self.isManuallyAdded, forKey: "ma")
|
||||
}
|
||||
|
||||
public static func ==(lhs: WebpagePreviewMessageAttribute, rhs: WebpagePreviewMessageAttribute) -> Bool {
|
||||
if lhs.leadingPreview != rhs.leadingPreview {
|
||||
return false
|
||||
}
|
||||
if lhs.forceLargeMedia != rhs.forceLargeMedia {
|
||||
return false
|
||||
}
|
||||
if lhs.isManuallyAdded != rhs.isManuallyAdded {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,64 @@ public enum EnqueueMessageGrouping {
|
|||
}
|
||||
|
||||
public struct EngineMessageReplyQuote: Codable, Equatable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case text = "t"
|
||||
case entities = "e"
|
||||
case media = "m"
|
||||
}
|
||||
|
||||
public var text: String
|
||||
public var entities: [MessageTextEntity]
|
||||
public var media: Media?
|
||||
|
||||
public init(text: String, entities: [MessageTextEntity]) {
|
||||
public init(text: String, entities: [MessageTextEntity], media: Media?) {
|
||||
self.text = text
|
||||
self.entities = entities
|
||||
self.media = media
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
self.text = try container.decode(String.self, forKey: .text)
|
||||
self.entities = try container.decode([MessageTextEntity].self, forKey: .entities)
|
||||
|
||||
if let mediaData = try container.decodeIfPresent(Data.self, forKey: .media) {
|
||||
self.media = PostboxDecoder(buffer: MemoryBuffer(data: mediaData)).decodeRootObject() as? Media
|
||||
} else {
|
||||
self.media = nil
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
try container.encode(self.text, forKey: .text)
|
||||
try container.encode(self.entities, forKey: .entities)
|
||||
if let media = self.media {
|
||||
let mediaEncoder = PostboxEncoder()
|
||||
mediaEncoder.encodeRootObject(media)
|
||||
try container.encode(mediaEncoder.makeData(), forKey: .media)
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: EngineMessageReplyQuote, rhs: EngineMessageReplyQuote) -> Bool {
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.entities != rhs.entities {
|
||||
return false
|
||||
}
|
||||
if let lhsMedia = lhs.media, let rhsMedia = rhs.media {
|
||||
if !lhsMedia.isEqual(to: rhsMedia) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if (lhs.media == nil) != (rhs.media == nil) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -166,6 +218,8 @@ private func filterMessageAttributesForOutgoingMessage(_ attributes: [MessageAtt
|
|||
return true
|
||||
case _ as MediaSpoilerMessageAttribute:
|
||||
return true
|
||||
case _ as WebpagePreviewMessageAttribute:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
|
@ -510,7 +564,16 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
|||
threadMessageId = replyMessage.effectiveReplyThreadMessageId
|
||||
if quote == nil, replyToMessageId.messageId.peerId != peerId {
|
||||
let nsText = replyMessage.text as NSString
|
||||
quote = EngineMessageReplyQuote(text: replyMessage.text, entities: messageTextEntitiesInRange(entities: replyMessage.textEntitiesAttribute?.entities ?? [], range: NSRange(location: 0, length: nsText.length), onlyQuoteable: true))
|
||||
var replyMedia: Media?
|
||||
for m in replyMessage.media {
|
||||
switch m {
|
||||
case _ as TelegramMediaImage, _ as TelegramMediaFile:
|
||||
replyMedia = m
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
quote = EngineMessageReplyQuote(text: replyMessage.text, entities: messageTextEntitiesInRange(entities: replyMessage.textEntitiesAttribute?.entities ?? [], range: NSRange(location: 0, length: nsText.length), onlyQuoteable: true), media: replyMedia)
|
||||
}
|
||||
}
|
||||
attributes.append(ReplyMessageAttribute(messageId: replyToMessageId.messageId, threadMessageId: threadMessageId, quote: quote))
|
||||
|
|
|
|||
|
|
@ -95,6 +95,21 @@ func messageContentToUpload(accountPeerId: PeerId, network: Network, postbox: Po
|
|||
return .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaStory(peer: inputPeer, id: media.storyId.id), ""), reuploadInfo: nil, cacheReferenceKey: nil))
|
||||
}
|
||||
|> castError(PendingMessageUploadError.self), .text)
|
||||
} else if let media = media.first as? TelegramMediaWebpage, case let .Loaded(content) = media.content {
|
||||
return .signal(postbox.transaction { transaction -> PendingMessageUploadedContentResult in
|
||||
var flags: Int32 = 0
|
||||
if let attribute = attributes.first(where: { $0 is WebpagePreviewMessageAttribute }) as? WebpagePreviewMessageAttribute {
|
||||
if let forceLargeMedia = attribute.forceLargeMedia {
|
||||
if forceLargeMedia {
|
||||
flags |= 1 << 0
|
||||
} else {
|
||||
flags |= 1 << 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaWebPage(flags: flags, url: content.url), text), reuploadInfo: nil, cacheReferenceKey: nil))
|
||||
}
|
||||
|> castError(PendingMessageUploadError.self), .text)
|
||||
} else if let media = media.first, let mediaResult = mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, forceReupload: forceReupload, isGrouped: isGrouped, passFetchProgress: passFetchProgress, forceNoBigParts: forceNoBigParts, peerId: peerId, media: media, text: text, autoremoveMessageAttribute: autoremoveMessageAttribute, autoclearMessageAttribute: autoclearMessageAttribute, messageId: messageId, attributes: attributes) {
|
||||
return .signal(mediaResult, .media)
|
||||
} else {
|
||||
|
|
@ -222,6 +237,18 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post
|
|||
} else if let media = media as? TelegramMediaDice {
|
||||
let inputDice = Api.InputMedia.inputMediaDice(emoticon: media.emoji)
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputDice, text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||
} else if let media = media as? TelegramMediaWebpage, case let .Loaded(content) = media.content {
|
||||
var flags: Int32 = 0
|
||||
if let attribute = attributes.first(where: { $0 is WebpagePreviewMessageAttribute }) as? WebpagePreviewMessageAttribute {
|
||||
if let forceLargeMedia = attribute.forceLargeMedia {
|
||||
if forceLargeMedia {
|
||||
flags |= 1 << 0
|
||||
} else {
|
||||
flags |= 1 << 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaWebPage(flags: flags, url: content.url), text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ private final class PendingUpdateMessageManagerImpl {
|
|||
}
|
||||
}
|
||||
|
||||
func add(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], disableUrlPreview: Bool) {
|
||||
func add(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool) {
|
||||
if let context = self.contexts[messageId] {
|
||||
self.contexts.removeValue(forKey: messageId)
|
||||
context.disposable.dispose()
|
||||
|
|
@ -75,7 +75,7 @@ private final class PendingUpdateMessageManagerImpl {
|
|||
self.contexts[messageId] = context
|
||||
|
||||
let queue = self.queue
|
||||
disposable.set((requestEditMessage(accountPeerId: self.stateManager.accountPeerId, postbox: self.postbox, network: self.network, stateManager: self.stateManager, transformOutgoingMessageMedia: self.transformOutgoingMessageMedia, messageMediaPreuploadManager: self.messageMediaPreuploadManager, mediaReferenceRevalidationContext: self.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, disableUrlPreview: disableUrlPreview, scheduleTime: nil)
|
||||
disposable.set((requestEditMessage(accountPeerId: self.stateManager.accountPeerId, postbox: self.postbox, network: self.network, stateManager: self.stateManager, transformOutgoingMessageMedia: self.transformOutgoingMessageMedia, messageMediaPreuploadManager: self.messageMediaPreuploadManager, mediaReferenceRevalidationContext: self.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: nil)
|
||||
|> deliverOn(self.queue)).start(next: { [weak self, weak context] value in
|
||||
queue.async {
|
||||
guard let strongSelf = self, let initialContext = context else {
|
||||
|
|
@ -163,9 +163,9 @@ public final class PendingUpdateMessageManager {
|
|||
})
|
||||
}
|
||||
|
||||
public func add(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], disableUrlPreview: Bool = false) {
|
||||
public func add(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute? = nil, disableUrlPreview: Bool = false) {
|
||||
self.impl.with { impl in
|
||||
impl.add(messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, disableUrlPreview: disableUrlPreview)
|
||||
impl.add(messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,15 +27,15 @@ public enum RequestEditMessageError {
|
|||
case invalidGrouping
|
||||
}
|
||||
|
||||
func _internal_requestEditMessage(account: Account, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], disableUrlPreview: Bool = false, scheduleTime: Int32? = nil) -> Signal<RequestEditMessageResult, RequestEditMessageError> {
|
||||
return requestEditMessage(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, stateManager: account.stateManager, transformOutgoingMessageMedia: account.transformOutgoingMessageMedia, messageMediaPreuploadManager: account.messageMediaPreuploadManager, mediaReferenceRevalidationContext: account.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime)
|
||||
func _internal_requestEditMessage(account: Account, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool, scheduleTime: Int32?) -> Signal<RequestEditMessageResult, RequestEditMessageError> {
|
||||
return requestEditMessage(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, stateManager: account.stateManager, transformOutgoingMessageMedia: account.transformOutgoingMessageMedia, messageMediaPreuploadManager: account.messageMediaPreuploadManager, mediaReferenceRevalidationContext: account.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime)
|
||||
}
|
||||
|
||||
func requestEditMessage(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], disableUrlPreview: Bool, scheduleTime: Int32?) -> Signal<RequestEditMessageResult, RequestEditMessageError> {
|
||||
return requestEditMessageInternal(accountPeerId: accountPeerId, postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime, forceReupload: false)
|
||||
func requestEditMessage(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool, scheduleTime: Int32?) -> Signal<RequestEditMessageResult, RequestEditMessageError> {
|
||||
return requestEditMessageInternal(accountPeerId: accountPeerId, postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime, forceReupload: false)
|
||||
|> `catch` { error -> Signal<RequestEditMessageResult, RequestEditMessageInternalError> in
|
||||
if case .invalidReference = error {
|
||||
return requestEditMessageInternal(accountPeerId: accountPeerId, postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime, forceReupload: true)
|
||||
return requestEditMessageInternal(accountPeerId: accountPeerId, postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime, forceReupload: true)
|
||||
} else {
|
||||
return .fail(error)
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ func requestEditMessage(accountPeerId: PeerId, postbox: Postbox, network: Networ
|
|||
}
|
||||
}
|
||||
|
||||
private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], disableUrlPreview: Bool, scheduleTime: Int32?, forceReupload: Bool) -> Signal<RequestEditMessageResult, RequestEditMessageInternalError> {
|
||||
private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool, scheduleTime: Int32?, forceReupload: Bool) -> Signal<RequestEditMessageResult, RequestEditMessageInternalError> {
|
||||
let uploadedMedia: Signal<PendingMessageUploadedContentResult?, NoError>
|
||||
switch media {
|
||||
case .keep:
|
||||
|
|
@ -59,7 +59,11 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox,
|
|||
case let .update(media):
|
||||
let generateUploadSignal: (Bool) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>? = { forceReupload in
|
||||
let augmentedMedia = augmentMediaWithReference(media)
|
||||
return mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: stateManager.auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: mediaReferenceRevalidationContext, forceReupload: forceReupload, isGrouped: false, passFetchProgress: false, forceNoBigParts: false, peerId: messageId.peerId, media: augmentedMedia, text: "", autoremoveMessageAttribute: nil, autoclearMessageAttribute: nil, messageId: nil, attributes: [])
|
||||
var attributes: [MessageAttribute] = []
|
||||
if let webpagePreviewAttribute = webpagePreviewAttribute {
|
||||
attributes.append(webpagePreviewAttribute)
|
||||
}
|
||||
return mediaContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: stateManager.auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: mediaReferenceRevalidationContext, forceReupload: forceReupload, isGrouped: false, passFetchProgress: false, forceNoBigParts: false, peerId: messageId.peerId, media: augmentedMedia, text: "", autoremoveMessageAttribute: nil, autoclearMessageAttribute: nil, messageId: nil, attributes: attributes)
|
||||
}
|
||||
if let uploadSignal = generateUploadSignal(forceReupload) {
|
||||
uploadedMedia = .single(.progress(0.027))
|
||||
|
|
@ -164,6 +168,12 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox,
|
|||
flags |= Int32(1 << 15)
|
||||
}
|
||||
|
||||
if let webpagePreviewAttribute = webpagePreviewAttribute {
|
||||
if webpagePreviewAttribute.leadingPreview {
|
||||
flags |= Int32(1 << 16)
|
||||
}
|
||||
}
|
||||
|
||||
return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: text, media: inputMedia, replyMarkup: nil, entities: apiEntities, scheduleDate: effectiveScheduleTime))
|
||||
|> map { result -> Api.Updates? in
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -1148,9 +1148,15 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
|||
let messageText = text
|
||||
var medias: [Media] = []
|
||||
|
||||
let (mediaValue, expirationTimer, nonPremium, hasSpoiler) = textMediaAndExpirationTimerFromApiMedia(media, peerId)
|
||||
let (mediaValue, expirationTimer, nonPremium, hasSpoiler, webpageAttributes) = textMediaAndExpirationTimerFromApiMedia(media, peerId)
|
||||
if let mediaValue = mediaValue {
|
||||
medias.append(mediaValue)
|
||||
|
||||
if mediaValue is TelegramMediaWebpage {
|
||||
if let webpageAttributes {
|
||||
attributes.append(WebpagePreviewMessageAttribute(leadingPreview: false, forceLargeMedia: webpageAttributes.forceLargeMedia, isManuallyAdded: webpageAttributes.isManuallyAdded))
|
||||
}
|
||||
}
|
||||
}
|
||||
if let expirationTimer = expirationTimer {
|
||||
attributes.append(AutoclearTimeoutMessageAttribute(timeout: expirationTimer, countdownBeginTime: nil))
|
||||
|
|
@ -1532,15 +1538,17 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
|||
var replySubject: EngineMessageReplySubject?
|
||||
if let replyToMsgHeader = replyToMsgHeader {
|
||||
switch replyToMsgHeader {
|
||||
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, _, replyToTopId, quoteText, quoteEntities):
|
||||
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, replyMedia, replyToTopId, quoteText, quoteEntities):
|
||||
let _ = replyHeader
|
||||
let _ = replyMedia
|
||||
let _ = replyToTopId
|
||||
|
||||
var quote: EngineMessageReplyQuote?
|
||||
if let quoteText = quoteText {
|
||||
quote = EngineMessageReplyQuote(
|
||||
text: quoteText,
|
||||
entities: messageTextEntitiesFromApiEntities(quoteEntities ?? [])
|
||||
entities: messageTextEntitiesFromApiEntities(quoteEntities ?? []),
|
||||
media: textMediaAndExpirationTimerFromApiMedia(replyMedia, accountPeerId).media
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -4492,7 +4500,7 @@ func replayFinalState(
|
|||
}
|
||||
updatedExtendedMedia = .preview(dimensions: dimensions, immediateThumbnailData: immediateThumbnailData, videoDuration: videoDuration)
|
||||
case let .messageExtendedMedia(apiMedia):
|
||||
let (media, _, _, _) = textMediaAndExpirationTimerFromApiMedia(apiMedia, currentMessage.id.peerId)
|
||||
let (media, _, _, _, _) = textMediaAndExpirationTimerFromApiMedia(apiMedia, currentMessage.id.peerId)
|
||||
if let media = media {
|
||||
updatedExtendedMedia = .full(media: media)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
|
|||
text = updatedMessage.text
|
||||
forwardInfo = updatedMessage.forwardInfo
|
||||
} else if case let .updateShortSentMessage(_, _, _, _, _, apiMedia, entities, ttlPeriod) = result {
|
||||
let (mediaValue, _, nonPremium, hasSpoiler) = textMediaAndExpirationTimerFromApiMedia(apiMedia, currentMessage.id.peerId)
|
||||
let (mediaValue, _, nonPremium, hasSpoiler, _) = textMediaAndExpirationTimerFromApiMedia(apiMedia, currentMessage.id.peerId)
|
||||
if let mediaValue = mediaValue {
|
||||
media = [mediaValue]
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1260,6 +1260,12 @@ public final class PendingMessageManager {
|
|||
}
|
||||
}
|
||||
|
||||
if let attribute = message.webpagePreviewAttribute {
|
||||
if attribute.leadingPreview {
|
||||
flags |= 1 << 16
|
||||
}
|
||||
}
|
||||
|
||||
sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer), info: .acknowledgement, tag: dependencyTag)
|
||||
case let .media(inputMedia, text):
|
||||
if bubbleUpEmojiOrStickersets {
|
||||
|
|
@ -1312,6 +1318,12 @@ public final class PendingMessageManager {
|
|||
replyTo = .inputReplyToStory(userId: inputUser, storyId: replyToStoryId.id)
|
||||
}
|
||||
}
|
||||
|
||||
if let attribute = message.webpagePreviewAttribute {
|
||||
if attribute.leadingPreview {
|
||||
flags |= 1 << 16
|
||||
}
|
||||
}
|
||||
|
||||
sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer), tag: dependencyTag)
|
||||
|> map(NetworkRequestResult.result)
|
||||
|
|
|
|||
|
|
@ -56,6 +56,14 @@ public class QuotedReplyMessageAttribute: MessageAttribute {
|
|||
return []
|
||||
}
|
||||
|
||||
public var associatedPeerIds: [PeerId] {
|
||||
if let peerId = self.peerId {
|
||||
return [peerId]
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
public init(peerId: PeerId?, authorName: String?, quote: EngineMessageReplyQuote?) {
|
||||
self.peerId = peerId
|
||||
self.authorName = authorName
|
||||
|
|
|
|||
|
|
@ -69,34 +69,6 @@ public final class TelegraMediaWebpageThemeAttribute: PostboxCoding, Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
public struct TelegramMediaWebpageDisplayOptions: Codable, Equatable {
|
||||
public enum CodingKeys: String, CodingKey {
|
||||
case position = "p"
|
||||
case largeMedia = "lm"
|
||||
}
|
||||
|
||||
public enum Position: Int32, Codable {
|
||||
case aboveText = 0
|
||||
case belowText = 1
|
||||
}
|
||||
|
||||
public var position: Position?
|
||||
public var largeMedia: Bool?
|
||||
|
||||
public static let `default` = TelegramMediaWebpageDisplayOptions(
|
||||
position: nil,
|
||||
largeMedia: nil
|
||||
)
|
||||
|
||||
public init(
|
||||
position: Position?,
|
||||
largeMedia: Bool?
|
||||
) {
|
||||
self.position = position
|
||||
self.largeMedia = largeMedia
|
||||
}
|
||||
}
|
||||
|
||||
public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
|
||||
public let url: String
|
||||
public let displayUrl: String
|
||||
|
|
@ -110,6 +82,7 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
|
|||
public let embedSize: PixelDimensions?
|
||||
public let duration: Int?
|
||||
public let author: String?
|
||||
public let isMediaLargeByDefault: Bool?
|
||||
|
||||
public let image: TelegramMediaImage?
|
||||
public let file: TelegramMediaFile?
|
||||
|
|
@ -117,8 +90,6 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
|
|||
public let attributes: [TelegramMediaWebpageAttribute]
|
||||
public let instantPage: InstantPage?
|
||||
|
||||
public let displayOptions: TelegramMediaWebpageDisplayOptions
|
||||
|
||||
public init(
|
||||
url: String,
|
||||
displayUrl: String,
|
||||
|
|
@ -132,12 +103,12 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
|
|||
embedSize: PixelDimensions?,
|
||||
duration: Int?,
|
||||
author: String?,
|
||||
isMediaLargeByDefault: Bool?,
|
||||
image: TelegramMediaImage?,
|
||||
file: TelegramMediaFile?,
|
||||
story: TelegramMediaStory?,
|
||||
attributes: [TelegramMediaWebpageAttribute],
|
||||
instantPage: InstantPage?,
|
||||
displayOptions: TelegramMediaWebpageDisplayOptions
|
||||
instantPage: InstantPage?
|
||||
) {
|
||||
self.url = url
|
||||
self.displayUrl = displayUrl
|
||||
|
|
@ -151,12 +122,12 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
|
|||
self.embedSize = embedSize
|
||||
self.duration = duration
|
||||
self.author = author
|
||||
self.isMediaLargeByDefault = isMediaLargeByDefault
|
||||
self.image = image
|
||||
self.file = file
|
||||
self.story = story
|
||||
self.attributes = attributes
|
||||
self.instantPage = instantPage
|
||||
self.displayOptions = displayOptions
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
|
|
@ -180,6 +151,7 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
|
|||
self.duration = nil
|
||||
}
|
||||
self.author = decoder.decodeOptionalStringForKey("au")
|
||||
self.isMediaLargeByDefault = decoder.decodeOptionalBoolForKey("lbd")
|
||||
|
||||
if let image = decoder.decodeObjectForKey("im") as? TelegramMediaImage {
|
||||
self.image = image
|
||||
|
|
@ -213,8 +185,6 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
|
|||
} else {
|
||||
self.instantPage = nil
|
||||
}
|
||||
|
||||
self.displayOptions = decoder.decodeCodable(TelegramMediaWebpageDisplayOptions.self, forKey: "do") ?? TelegramMediaWebpageDisplayOptions.default
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
|
|
@ -268,6 +238,11 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
|
|||
} else {
|
||||
encoder.encodeNil(forKey: "au")
|
||||
}
|
||||
if let isMediaLargeByDefault = self.isMediaLargeByDefault {
|
||||
encoder.encodeBool(isMediaLargeByDefault, forKey: "lbd")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "lbd")
|
||||
}
|
||||
if let image = self.image {
|
||||
encoder.encodeObject(image, forKey: "im")
|
||||
} else {
|
||||
|
|
@ -291,31 +266,6 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
|
|||
} else {
|
||||
encoder.encodeNil(forKey: "ip")
|
||||
}
|
||||
|
||||
encoder.encodeCodable(self.displayOptions, forKey: "do")
|
||||
}
|
||||
|
||||
public func withDisplayOptions(_ displayOptions: TelegramMediaWebpageDisplayOptions) -> TelegramMediaWebpageLoadedContent {
|
||||
return TelegramMediaWebpageLoadedContent(
|
||||
url: self.url,
|
||||
displayUrl: self.displayUrl,
|
||||
hash: self.hash,
|
||||
type: self.type,
|
||||
websiteName: self.websiteName,
|
||||
title: self.title,
|
||||
text: self.text,
|
||||
embedUrl: self.embedUrl,
|
||||
embedType: self.embedType,
|
||||
embedSize: self.embedSize,
|
||||
duration: self.duration,
|
||||
author: self.author,
|
||||
image: self.image,
|
||||
file: self.file,
|
||||
story: self.story,
|
||||
attributes: self.attributes,
|
||||
instantPage: self.instantPage,
|
||||
displayOptions: displayOptions
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -335,6 +285,10 @@ public func ==(lhs: TelegramMediaWebpageLoadedContent, rhs: TelegramMediaWebpage
|
|||
return false
|
||||
}
|
||||
|
||||
if lhs.isMediaLargeByDefault != rhs.isMediaLargeByDefault {
|
||||
return false
|
||||
}
|
||||
|
||||
if let lhsImage = lhs.image, let rhsImage = rhs.image {
|
||||
if !lhsImage.isEqual(to: rhsImage) {
|
||||
return false
|
||||
|
|
@ -373,10 +327,6 @@ public func ==(lhs: TelegramMediaWebpageLoadedContent, rhs: TelegramMediaWebpage
|
|||
return false
|
||||
}
|
||||
|
||||
if lhs.displayOptions != rhs.displayOptions {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -330,7 +330,9 @@ public func messageTextEntitiesInRange(entities: [MessageTextEntity], range: NSR
|
|||
}
|
||||
}
|
||||
if entity.range.overlaps(range) {
|
||||
result.append(MessageTextEntity(range: entity.range.clamped(to: range), type: entity.type))
|
||||
var mappedRange = entity.range.clamped(to: range)
|
||||
mappedRange = (entity.range.lowerBound - range.lowerBound) ..< (entity.range.upperBound - range.lowerBound)
|
||||
result.append(MessageTextEntity(range: mappedRange, type: entity.type))
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -1052,7 +1052,7 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId
|
|||
}
|
||||
|
||||
id = idValue
|
||||
let (parsedMedia, _, _, _) = textMediaAndExpirationTimerFromApiMedia(media, toPeerId)
|
||||
let (parsedMedia, _, _, _, _) = textMediaAndExpirationTimerFromApiMedia(media, toPeerId)
|
||||
if let parsedMedia = parsedMedia {
|
||||
applyMediaResourceChanges(from: originalMedia, to: parsedMedia, postbox: postbox, force: originalMedia is TelegramMediaFile && parsedMedia is TelegramMediaFile)
|
||||
}
|
||||
|
|
@ -1170,7 +1170,7 @@ func _internal_editStory(account: Account, peerId: PeerId, id: Int32, media: Eng
|
|||
if case let .updateStory(_, story) = update {
|
||||
switch story {
|
||||
case let .storyItem(_, _, _, _, _, _, media, _, _, _, _):
|
||||
let (parsedMedia, _, _, _) = textMediaAndExpirationTimerFromApiMedia(media, account.peerId)
|
||||
let (parsedMedia, _, _, _, _) = textMediaAndExpirationTimerFromApiMedia(media, account.peerId)
|
||||
if let parsedMedia = parsedMedia, let originalMedia = originalMedia {
|
||||
applyMediaResourceChanges(from: originalMedia, to: parsedMedia, postbox: account.postbox, force: false)
|
||||
}
|
||||
|
|
@ -1540,7 +1540,7 @@ extension Stories.StoredItem {
|
|||
init?(apiStoryItem: Api.StoryItem, existingItem: Stories.Item? = nil, peerId: PeerId, transaction: Transaction) {
|
||||
switch apiStoryItem {
|
||||
case let .storyItem(flags, id, date, expireDate, caption, entities, media, mediaAreas, privacy, views, sentReaction):
|
||||
let (parsedMedia, _, _, _) = textMediaAndExpirationTimerFromApiMedia(media, peerId)
|
||||
let (parsedMedia, _, _, _, _) = textMediaAndExpirationTimerFromApiMedia(media, peerId)
|
||||
if let parsedMedia = parsedMedia {
|
||||
var parsedPrivacy: Stories.Item.Privacy?
|
||||
if let privacy = privacy {
|
||||
|
|
|
|||
|
|
@ -118,8 +118,8 @@ public extension TelegramEngine {
|
|||
return _internal_clearAuthorHistory(account: self.account, peerId: peerId, memberId: memberId)
|
||||
}
|
||||
|
||||
public func requestEditMessage(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], disableUrlPreview: Bool = false, scheduleTime: Int32? = nil) -> Signal<RequestEditMessageResult, RequestEditMessageError> {
|
||||
return _internal_requestEditMessage(account: self.account, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime)
|
||||
public func requestEditMessage(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute? = nil, disableUrlPreview: Bool = false, scheduleTime: Int32? = nil) -> Signal<RequestEditMessageResult, RequestEditMessageError> {
|
||||
return _internal_requestEditMessage(account: self.account, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime)
|
||||
}
|
||||
|
||||
public func requestEditLiveLocation(messageId: MessageId, stop: Bool, coordinate: (latitude: Double, longitude: Double, accuracyRadius: Int32?)?, heading: Int32?, proximityNotificationRadius: Int32?) -> Signal<Void, NoError> {
|
||||
|
|
|
|||
|
|
@ -437,6 +437,17 @@ public extension Message {
|
|||
}
|
||||
}
|
||||
|
||||
public extension Message {
|
||||
var webpagePreviewAttribute: WebpagePreviewMessageAttribute? {
|
||||
for attribute in self.attributes {
|
||||
if let attribute = attribute as? WebpagePreviewMessageAttribute {
|
||||
return attribute
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func _internal_parseMediaAttachment(data: Data) -> Media? {
|
||||
guard let object = Api.parse(Buffer(buffer: MemoryBuffer(data: data))) else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -93,7 +93,26 @@ public func actualizedWebpage(account: Account, webpage: TelegramMediaWebpage) -
|
|||
if let updatedWebpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId == webpage.webpageId {
|
||||
return .single(updatedWebpage)
|
||||
} else if case let .webPageNotModified(_, viewsValue) = apiWebpage, let views = viewsValue, case let .Loaded(content) = webpage.content {
|
||||
let updatedContent: TelegramMediaWebpageContent = .Loaded(TelegramMediaWebpageLoadedContent(url: content.url, displayUrl: content.displayUrl, hash: content.hash, type: content.type, websiteName: content.websiteName, title: content.title, text: content.text, embedUrl: content.embedUrl, embedType: content.embedType, embedSize: content.embedSize, duration: content.duration, author: content.author, image: content.image, file: content.file, story: content.story, attributes: content.attributes, instantPage: content.instantPage.flatMap({ InstantPage(blocks: $0.blocks, media: $0.media, isComplete: $0.isComplete, rtl: $0.rtl, url: $0.url, views: views) }), displayOptions: .default))
|
||||
let updatedContent: TelegramMediaWebpageContent = .Loaded(TelegramMediaWebpageLoadedContent(
|
||||
url: content.url,
|
||||
displayUrl: content.displayUrl,
|
||||
hash: content.hash,
|
||||
type: content.type,
|
||||
websiteName: content.websiteName,
|
||||
title: content.title,
|
||||
text: content.text,
|
||||
embedUrl: content.embedUrl,
|
||||
embedType: content.embedType,
|
||||
embedSize: content.embedSize,
|
||||
duration: content.duration,
|
||||
author: content.author,
|
||||
isMediaLargeByDefault: content.isMediaLargeByDefault,
|
||||
image: content.image,
|
||||
file: content.file,
|
||||
story: content.story,
|
||||
attributes: content.attributes,
|
||||
instantPage: content.instantPage.flatMap({ InstantPage(blocks: $0.blocks, media: $0.media, isComplete: $0.isComplete, rtl: $0.rtl, url: $0.url, views: views) })
|
||||
))
|
||||
let updatedWebpage = TelegramMediaWebpage(webpageId: webpage.webpageId, content: updatedContent)
|
||||
updateMessageMedia(transaction: transaction, id: webpage.webpageId, media: updatedWebpage)
|
||||
return .single(updatedWebpage)
|
||||
|
|
|
|||
|
|
@ -1287,9 +1287,10 @@ public struct PresentationResourcesChat {
|
|||
|
||||
public static func chatReplyBackgroundTemplateImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatReplyBackgroundTemplateImage.rawValue, { theme in
|
||||
let radius: CGFloat = 3.0
|
||||
let radius: CGFloat = 4.0
|
||||
let lineWidth: CGFloat = 3.0
|
||||
|
||||
return generateImage(CGSize(width: radius * 2.0 + 1.0, height: radius * 2.0), rotatedContext: { size, context in
|
||||
return generateImage(CGSize(width: radius * 2.0 + 4.0, height: radius * 2.0 + 8.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: radius).cgPath)
|
||||
|
|
@ -1299,8 +1300,8 @@ public struct PresentationResourcesChat {
|
|||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: radius, height: size.height)))
|
||||
})?.stretchableImage(withLeftCapWidth: Int(radius), topCapHeight: Int(radius)).withRenderingMode(.alwaysTemplate)
|
||||
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: lineWidth, height: size.height)))
|
||||
})?.stretchableImage(withLeftCapWidth: Int(radius) + 2, topCapHeight: Int(radius) + 3).withRenderingMode(.alwaysTemplate)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -356,6 +356,12 @@ public func mediaContentKind(_ media: EngineMedia, message: EngineMessage? = nil
|
|||
return .story
|
||||
case .giveaway:
|
||||
return .giveaway
|
||||
case let .webpage(webpage):
|
||||
if let message, message.text.isEmpty, case let .Loaded(content) = webpage.content {
|
||||
return .text(NSAttributedString(string: content.displayUrl))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -427,7 +427,7 @@ public final class ChatBotInfoItemNode: ListViewItemNode {
|
|||
if let (attributeText, fullText) = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return .url(url: url, concealed: concealed)
|
||||
return .url(url: url, concealed: concealed, activate: nil)
|
||||
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
return .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)
|
||||
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
|
|
@ -454,8 +454,8 @@ public final class ChatBotInfoItemNode: ListViewItemNode {
|
|||
switch tapAction {
|
||||
case .none, .ignore:
|
||||
break
|
||||
case let .url(url, concealed):
|
||||
self.item?.controllerInteraction.openUrl(url, concealed, nil, nil)
|
||||
case let .url(url, concealed, activate):
|
||||
self.item?.controllerInteraction.openUrl(url, concealed, nil, nil, activate?())
|
||||
case let .peerMention(peerId, _, _):
|
||||
if let item = self.item {
|
||||
let _ = (item.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
|
|
@ -480,7 +480,7 @@ public final class ChatBotInfoItemNode: ListViewItemNode {
|
|||
switch tapAction {
|
||||
case .none, .ignore:
|
||||
break
|
||||
case let .url(url, _):
|
||||
case let .url(url, _, _):
|
||||
item.controllerInteraction.longTap(.url(url), nil)
|
||||
case let .peerMention(peerId, mention, _):
|
||||
item.controllerInteraction.longTap(.peerMention(peerId, mention), nil)
|
||||
|
|
|
|||
|
|
@ -378,7 +378,7 @@ public final class ChatButtonKeyboardInputNode: ChatInputNode {
|
|||
self.controllerInteraction.sendMessage(markupButton.title)
|
||||
dismissIfOnce = true
|
||||
case let .url(url):
|
||||
self.controllerInteraction.openUrl(url, true, nil, nil)
|
||||
self.controllerInteraction.openUrl(url, true, nil, nil, nil)
|
||||
case .requestMap:
|
||||
self.controllerInteraction.shareCurrentLocation()
|
||||
case .requestPhone:
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@
|
|||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
return [super canPerformAction:action withSender:sender];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -517,7 +517,7 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
if let (attributeText, fullText) = self.labelNode.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return .url(url: url, concealed: concealed)
|
||||
return .url(url: url, concealed: concealed, activate: nil)
|
||||
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
return .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: true)
|
||||
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
|
|
|
|||
|
|
@ -1967,7 +1967,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
return .optionalAction({
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId)
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text))
|
||||
})
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
return .optionalAction({
|
||||
|
|
@ -1990,7 +1990,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||
return
|
||||
}
|
||||
}
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId)
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId, NavigateToMessageParams(timestamp: nil, quote: nil))
|
||||
} else if let peer = forwardInfo.source ?? forwardInfo.author {
|
||||
item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
|
||||
} else if let _ = forwardInfo.authorSignature {
|
||||
|
|
@ -2269,7 +2269,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||
} else if item.content.firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) {
|
||||
for attribute in item.content.firstMessage.attributes {
|
||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||
item.controllerInteraction.navigateToMessage(item.content.firstMessage.id, attribute.messageId)
|
||||
item.controllerInteraction.navigateToMessage(item.content.firstMessage.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: nil))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,18 +7,16 @@ import ChatPresentationInterfaceState
|
|||
import ShimmerEffect
|
||||
|
||||
private let buttonFont = Font.semibold(14.0)
|
||||
private let sharedBackgroundImage = generateStretchableFilledCircleImage(radius: 4.0, color: UIColor.white)?.withRenderingMode(.alwaysTemplate)
|
||||
|
||||
public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButtonNode {
|
||||
private let textNode: TextNode
|
||||
private let iconNode: ASImageNode
|
||||
private let highlightedTextNode: TextNode
|
||||
private let backgroundNode: ASImageNode
|
||||
private var iconView: UIImageView?
|
||||
private let shimmerEffectNode: ShimmerEffectForegroundNode
|
||||
|
||||
private var regularImage: UIImage?
|
||||
private var highlightedImage: UIImage?
|
||||
private var backgroundView: UIImageView?
|
||||
|
||||
private var regularIconImage: UIImage?
|
||||
private var highlightedIconImage: UIImage?
|
||||
|
||||
public var pressed: (() -> Void)?
|
||||
|
||||
|
|
@ -27,56 +25,24 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton
|
|||
public init() {
|
||||
self.textNode = TextNode()
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
self.highlightedTextNode = TextNode()
|
||||
self.highlightedTextNode.isUserInteractionEnabled = false
|
||||
|
||||
self.shimmerEffectNode = ShimmerEffectForegroundNode()
|
||||
self.shimmerEffectNode.cornerRadius = 5.0
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.displayWithoutProcessing = true
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.isLayerBacked = true
|
||||
self.iconNode.displayWithoutProcessing = true
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.shimmerEffectNode)
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.highlightedTextNode)
|
||||
self.highlightedTextNode.isHidden = true
|
||||
|
||||
self.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.backgroundNode.image = strongSelf.highlightedImage
|
||||
strongSelf.iconNode.image = strongSelf.highlightedIconImage
|
||||
strongSelf.textNode.isHidden = true
|
||||
strongSelf.highlightedTextNode.isHidden = false
|
||||
|
||||
let scale = (strongSelf.bounds.width - 10.0) / strongSelf.bounds.width
|
||||
strongSelf.layer.animateScale(from: 1.0, to: scale, duration: 0.15, removeOnCompletion: false)
|
||||
} else {
|
||||
if let presentationLayer = strongSelf.layer.presentation() {
|
||||
strongSelf.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.25, removeOnCompletion: false)
|
||||
}
|
||||
if let snapshot = strongSelf.view.snapshotView(afterScreenUpdates: false) {
|
||||
strongSelf.view.addSubview(snapshot)
|
||||
|
||||
snapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
snapshot.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
strongSelf.backgroundNode.image = strongSelf.regularImage
|
||||
strongSelf.iconNode.image = strongSelf.regularIconImage
|
||||
strongSelf.textNode.isHidden = false
|
||||
strongSelf.highlightedTextNode.isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -95,7 +61,7 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton
|
|||
self.shimmerEffectNode.isHidden = false
|
||||
self.shimmerEffectNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
|
||||
let backgroundFrame = self.backgroundNode.frame
|
||||
let backgroundFrame = self.bounds
|
||||
self.shimmerEffectNode.frame = backgroundFrame
|
||||
self.shimmerEffectNode.updateAbsoluteRect(CGRect(origin: .zero, size: backgroundFrame.size), within: backgroundFrame.size)
|
||||
self.shimmerEffectNode.update(backgroundColor: .clear, foregroundColor: titleColor.withAlphaComponent(0.3), horizontal: true, effectSize: nil, globalTimeOffset: false, duration: nil)
|
||||
|
|
@ -107,16 +73,13 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton
|
|||
})
|
||||
}
|
||||
|
||||
public static func asyncLayout(_ current: ChatMessageAttachedContentButtonNode?) -> (_ width: CGFloat, _ regularImage: UIImage?, _ highlightedImage: UIImage?, _ iconImage: UIImage?, _ highlightedIconImage: UIImage?, _ cornerIcon: Bool, _ title: String, _ titleColor: UIColor, _ highlightedTitleColor: UIColor, _ inProgress: Bool) -> (CGFloat, (CGFloat, CGFloat) -> (CGSize, () -> ChatMessageAttachedContentButtonNode)) {
|
||||
let previousRegularImage = current?.regularImage
|
||||
let previousHighlightedImage = current?.highlightedImage
|
||||
public typealias AsyncLayout = (_ width: CGFloat, _ iconImage: UIImage?, _ cornerIcon: Bool, _ title: String, _ titleColor: UIColor, _ inProgress: Bool, _ drawBackground: Bool) -> (CGFloat, (CGFloat, CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageAttachedContentButtonNode))
|
||||
public static func asyncLayout(_ current: ChatMessageAttachedContentButtonNode?) -> AsyncLayout {
|
||||
let previousRegularIconImage = current?.regularIconImage
|
||||
let previousHighlightedIconImage = current?.highlightedIconImage
|
||||
|
||||
let maybeMakeTextLayout = (current?.textNode).flatMap(TextNode.asyncLayout)
|
||||
let maybeMakeHighlightedTextLayout = (current?.highlightedTextNode).flatMap(TextNode.asyncLayout)
|
||||
|
||||
return { width, regularImage, highlightedImage, iconImage, highlightedIconImage, cornerIcon, title, titleColor, highlightedTitleColor, inProgress in
|
||||
return { width, iconImage, cornerIcon, title, titleColor, inProgress, drawBackground in
|
||||
let targetNode: ChatMessageAttachedContentButtonNode
|
||||
if let current = current {
|
||||
targetNode = current
|
||||
|
|
@ -131,33 +94,11 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton
|
|||
makeTextLayout = TextNode.asyncLayout(targetNode.textNode)
|
||||
}
|
||||
|
||||
let makeHighlightedTextLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode)
|
||||
if let maybeMakeHighlightedTextLayout = maybeMakeHighlightedTextLayout {
|
||||
makeHighlightedTextLayout = maybeMakeHighlightedTextLayout
|
||||
} else {
|
||||
makeHighlightedTextLayout = TextNode.asyncLayout(targetNode.highlightedTextNode)
|
||||
}
|
||||
|
||||
var updatedRegularImage: UIImage?
|
||||
if regularImage !== previousRegularImage {
|
||||
updatedRegularImage = regularImage
|
||||
}
|
||||
|
||||
var updatedHighlightedImage: UIImage?
|
||||
if highlightedImage !== previousHighlightedImage {
|
||||
updatedHighlightedImage = highlightedImage
|
||||
}
|
||||
|
||||
var updatedRegularIconImage: UIImage?
|
||||
if iconImage !== previousRegularIconImage {
|
||||
updatedRegularIconImage = iconImage
|
||||
}
|
||||
|
||||
var updatedHighlightedIconImage: UIImage?
|
||||
if highlightedIconImage !== previousHighlightedIconImage {
|
||||
updatedHighlightedIconImage = highlightedIconImage
|
||||
}
|
||||
|
||||
var iconWidth: CGFloat = 0.0
|
||||
if let iconImage = iconImage {
|
||||
iconWidth = iconImage.size.width + 5.0
|
||||
|
|
@ -167,65 +108,70 @@ public final class ChatMessageAttachedContentButtonNode: HighlightTrackingButton
|
|||
|
||||
let (textSize, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: buttonFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(1.0, width - labelInset * 2.0 - iconWidth), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (_, highlightedTextApply) = makeHighlightedTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: buttonFont, textColor: highlightedTitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(1.0, width - labelInset * 2.0), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
return (textSize.size.width + labelInset * 2.0, { refinedWidth, refinedHeight in
|
||||
let size = CGSize(width: refinedWidth, height: refinedHeight)
|
||||
return (size, {
|
||||
return (size, { animation in
|
||||
targetNode.accessibilityLabel = title
|
||||
|
||||
//targetNode.borderColor = UIColor.red.cgColor
|
||||
//targetNode.borderWidth = 1.0
|
||||
|
||||
targetNode.titleColor = titleColor
|
||||
|
||||
if let updatedRegularImage = updatedRegularImage {
|
||||
targetNode.regularImage = updatedRegularImage
|
||||
if !targetNode.textNode.isHidden {
|
||||
targetNode.backgroundNode.image = updatedRegularImage
|
||||
}
|
||||
}
|
||||
if let updatedHighlightedImage = updatedHighlightedImage {
|
||||
targetNode.highlightedImage = updatedHighlightedImage
|
||||
if targetNode.textNode.isHidden {
|
||||
targetNode.backgroundNode.image = updatedHighlightedImage
|
||||
}
|
||||
let iconView: UIImageView
|
||||
if let current = targetNode.iconView {
|
||||
iconView = current
|
||||
} else {
|
||||
iconView = UIImageView()
|
||||
targetNode.iconView = iconView
|
||||
targetNode.view.addSubview(iconView)
|
||||
}
|
||||
iconView.tintColor = titleColor
|
||||
|
||||
if let updatedRegularIconImage = updatedRegularIconImage {
|
||||
targetNode.regularIconImage = updatedRegularIconImage
|
||||
if !targetNode.textNode.isHidden {
|
||||
targetNode.iconNode.image = updatedRegularIconImage
|
||||
}
|
||||
}
|
||||
if let updatedHighlightedIconImage = updatedHighlightedIconImage {
|
||||
targetNode.highlightedIconImage = updatedHighlightedIconImage
|
||||
if targetNode.iconNode.isHidden {
|
||||
targetNode.iconNode.image = updatedHighlightedIconImage
|
||||
iconView.image = updatedRegularIconImage.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
}
|
||||
|
||||
let _ = textApply()
|
||||
let _ = highlightedTextApply()
|
||||
|
||||
let backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: refinedWidth, height: size.height))
|
||||
|
||||
var textFrame = CGRect(origin: CGPoint(x: floor((refinedWidth - textSize.size.width) / 2.0), y: floor((backgroundFrame.height - textSize.size.height) / 2.0)), size: textSize.size)
|
||||
targetNode.backgroundNode.frame = backgroundFrame
|
||||
if let image = targetNode.iconNode.image {
|
||||
if drawBackground {
|
||||
textFrame.origin.y += 1.0
|
||||
}
|
||||
if let image = iconView.image {
|
||||
let iconFrame: CGRect
|
||||
if cornerIcon {
|
||||
targetNode.iconNode.frame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - image.size.width - 5.0, y: 5.0), size: image.size)
|
||||
iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - image.size.width - 5.0, y: 5.0), size: image.size)
|
||||
} else {
|
||||
textFrame.origin.x += floor(image.size.width / 2.0)
|
||||
targetNode.iconNode.frame = CGRect(origin: CGPoint(x: textFrame.minX - image.size.width - 5.0, y: textFrame.minY + floorToScreenPixels((textFrame.height - image.size.height) * 0.5)), size: image.size)
|
||||
iconFrame = CGRect(origin: CGPoint(x: textFrame.minX - image.size.width - 5.0, y: textFrame.minY + floorToScreenPixels((textFrame.height - image.size.height) * 0.5)), size: image.size)
|
||||
}
|
||||
if targetNode.iconNode.supernode == nil {
|
||||
targetNode.addSubnode(targetNode.iconNode)
|
||||
}
|
||||
} else if targetNode.iconNode.supernode != nil {
|
||||
targetNode.iconNode.removeFromSupernode()
|
||||
|
||||
animation.animator.updateFrame(layer: iconView.layer, frame: iconFrame, completion: nil)
|
||||
}
|
||||
|
||||
targetNode.textNode.frame = textFrame
|
||||
targetNode.highlightedTextNode.frame = targetNode.textNode.frame
|
||||
targetNode.textNode.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
|
||||
animation.animator.updatePosition(layer: targetNode.textNode.layer, position: textFrame.center, completion: nil)
|
||||
|
||||
if drawBackground {
|
||||
let backgroundView: UIImageView
|
||||
if let current = targetNode.backgroundView {
|
||||
backgroundView = current
|
||||
animation.animator.updateFrame(layer: backgroundView.layer, frame: backgroundFrame, completion: nil)
|
||||
} else {
|
||||
backgroundView = UIImageView()
|
||||
backgroundView.image = sharedBackgroundImage
|
||||
targetNode.backgroundView = backgroundView
|
||||
targetNode.view.insertSubview(backgroundView, at: 0)
|
||||
backgroundView.frame = backgroundFrame
|
||||
}
|
||||
backgroundView.tintColor = titleColor.withMultipliedAlpha(0.1)
|
||||
} else if let backgroundView = targetNode.backgroundView {
|
||||
targetNode.backgroundView = nil
|
||||
backgroundView.removeFromSuperview()
|
||||
}
|
||||
|
||||
return targetNode
|
||||
})
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -11,6 +11,7 @@ swift_library(
|
|||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramCore",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import ChatMessageBackground
|
|||
import ChatControllerInteraction
|
||||
import ChatHistoryEntry
|
||||
import ChatMessageItemCommon
|
||||
import SwiftSignalKit
|
||||
|
||||
public enum ChatMessageBubbleContentBackgroundHiding {
|
||||
case never
|
||||
|
|
@ -73,7 +74,10 @@ public enum ChatMessageBubbleMergeStatus {
|
|||
public enum ChatMessageBubbleRelativePosition {
|
||||
public enum NeighbourType {
|
||||
case media
|
||||
case freeform
|
||||
case header
|
||||
case footer
|
||||
case text
|
||||
case reactions
|
||||
}
|
||||
|
||||
public enum NeighbourSpacing {
|
||||
|
|
@ -119,7 +123,7 @@ public enum ChatMessageBubblePreparePosition {
|
|||
|
||||
public enum ChatMessageBubbleContentTapAction {
|
||||
case none
|
||||
case url(url: String, concealed: Bool)
|
||||
case url(url: String, concealed: Bool, activate: (() -> Promise<Bool>?)?)
|
||||
case textMention(String)
|
||||
case peerMention(peerId: PeerId, mention: String, openProfile: Bool)
|
||||
case botCommand(String)
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||
outer: for (message, itemAttributes) in item.content {
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? RestrictedContentMessageAttribute, attribute.platformText(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) != nil {
|
||||
result.append((message, ChatMessageRestrictedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageRestrictedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
needReactions = false
|
||||
break outer
|
||||
}
|
||||
|
|
@ -121,9 +121,9 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||
} else if let story = media as? TelegramMediaStory {
|
||||
if story.isMention {
|
||||
if let storyItem = message.associatedStories[story.storyId], storyItem.data.isEmpty {
|
||||
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
} else {
|
||||
result.append((message, ChatMessageStoryMentionContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageStoryMentionContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
}
|
||||
} else {
|
||||
var hideStory = false
|
||||
|
|
@ -154,7 +154,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||
if isVideo {
|
||||
if file.isInstantVideo {
|
||||
hasSeparateCommentsButton = true
|
||||
result.append((message, ChatMessageInstantVideoBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageInstantVideoBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
} else {
|
||||
if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported), message.text.isEmpty {
|
||||
messageWithCaptionToAdd = (message, itemAttributes)
|
||||
|
|
@ -168,30 +168,30 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||
}
|
||||
isFile = true
|
||||
hasFiles = true
|
||||
result.append((message, ChatMessageFileBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: neighborSpacing)))
|
||||
result.append((message, ChatMessageFileBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: neighborSpacing)))
|
||||
needReactions = false
|
||||
}
|
||||
} else if let action = media as? TelegramMediaAction {
|
||||
isAction = true
|
||||
if case .phoneCall = action.action {
|
||||
result.append((message, ChatMessageCallBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageCallBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
} else if case .giftPremium = action.action {
|
||||
result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
} else if case .suggestedProfilePhoto = action.action {
|
||||
result.append((message, ChatMessageProfilePhotoSuggestionContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageProfilePhotoSuggestionContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
} else if case .setChatWallpaper = action.action {
|
||||
result.append((message, ChatMessageWallpaperBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageWallpaperBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
} else if case .giftCode = action.action {
|
||||
result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
} else {
|
||||
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
}
|
||||
needReactions = false
|
||||
} else if let _ = media as? TelegramMediaMap {
|
||||
result.append((message, ChatMessageMapBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageMapBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
|
||||
} else if let _ = media as? TelegramMediaGame {
|
||||
skipText = true
|
||||
result.append((message, ChatMessageGameBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageGameBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
needReactions = false
|
||||
break inner
|
||||
} else if let invoice = media as? TelegramMediaInvoice {
|
||||
|
|
@ -199,23 +199,23 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
|
||||
} else {
|
||||
skipText = true
|
||||
result.append((message, ChatMessageInvoiceBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageInvoiceBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
}
|
||||
needReactions = false
|
||||
break inner
|
||||
} else if let _ = media as? TelegramMediaContact {
|
||||
result.append((message, ChatMessageContactBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageContactBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
needReactions = false
|
||||
} else if let _ = media as? TelegramMediaExpiredContent {
|
||||
result.removeAll()
|
||||
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
needReactions = false
|
||||
return (result, false, false)
|
||||
} else if let _ = media as? TelegramMediaPoll {
|
||||
result.append((message, ChatMessagePollBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessagePollBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
needReactions = false
|
||||
} else if let _ = media as? TelegramMediaGiveaway {
|
||||
result.append((message, ChatMessageGiveawayBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageGiveawayBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
needReactions = false
|
||||
} else if let _ = media as? TelegramMediaUnsupported {
|
||||
isUnsupportedMedia = true
|
||||
|
|
@ -235,7 +235,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||
messageWithCaptionToAdd = (message, itemAttributes)
|
||||
skipText = true
|
||||
} else {
|
||||
result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: isFile ? .condensed : .default)))
|
||||
result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)))
|
||||
needReactions = false
|
||||
}
|
||||
} else {
|
||||
|
|
@ -255,10 +255,10 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||
}
|
||||
}
|
||||
|
||||
if content.displayOptions.position == .aboveText {
|
||||
result.insert((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)), at: 0)
|
||||
if let attribute = message.attributes.first(where: { $0 is WebpagePreviewMessageAttribute }) as? WebpagePreviewMessageAttribute, attribute.leadingPreview {
|
||||
result.insert((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)), at: 0)
|
||||
} else {
|
||||
result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
}
|
||||
needReactions = false
|
||||
}
|
||||
|
|
@ -269,31 +269,31 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||
if message.adAttribute != nil {
|
||||
result.removeAll()
|
||||
|
||||
result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
needReactions = false
|
||||
}
|
||||
|
||||
if isUnsupportedMedia {
|
||||
result.append((message, ChatMessageUnsupportedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((message, ChatMessageUnsupportedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
needReactions = false
|
||||
}
|
||||
}
|
||||
|
||||
if let (messageWithCaptionToAdd, itemAttributes) = messageWithCaptionToAdd {
|
||||
result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
needReactions = false
|
||||
}
|
||||
|
||||
if let additionalContent = item.additionalContent {
|
||||
switch additionalContent {
|
||||
case let .eventLogPreviousMessage(previousMessage):
|
||||
result.append((previousMessage, ChatMessageEventLogPreviousMessageContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((previousMessage, ChatMessageEventLogPreviousMessageContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
needReactions = false
|
||||
case let .eventLogPreviousDescription(previousMessage):
|
||||
result.append((previousMessage, ChatMessageEventLogPreviousDescriptionContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((previousMessage, ChatMessageEventLogPreviousDescriptionContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
needReactions = false
|
||||
case let .eventLogPreviousLink(previousMessage):
|
||||
result.append((previousMessage, ChatMessageEventLogPreviousLinkContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((previousMessage, ChatMessageEventLogPreviousLinkContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
needReactions = false
|
||||
}
|
||||
}
|
||||
|
|
@ -308,28 +308,26 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||
|
||||
if !isAction && !hasSeparateCommentsButton && !Namespaces.Message.allScheduled.contains(firstMessage.id.namespace) {
|
||||
if hasCommentButton(item: item) {
|
||||
result.append((firstMessage, ChatMessageCommentFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((firstMessage, ChatMessageCommentFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .footer, neighborSpacing: .default)))
|
||||
}
|
||||
}
|
||||
|
||||
if !reactionsAreInline, let reactionsAttribute = mergedMessageReactions(attributes: firstMessage.attributes), !reactionsAttribute.reactions.isEmpty {
|
||||
if result.last?.1 == ChatMessageTextBubbleContentNode.self {
|
||||
} else {
|
||||
if result.last?.1 == ChatMessageWebpageBubbleContentNode.self ||
|
||||
result.last?.1 == ChatMessagePollBubbleContentNode.self ||
|
||||
if result.last?.1 == ChatMessagePollBubbleContentNode.self ||
|
||||
result.last?.1 == ChatMessageContactBubbleContentNode.self ||
|
||||
result.last?.1 == ChatMessageGameBubbleContentNode.self ||
|
||||
result.last?.1 == ChatMessageInvoiceBubbleContentNode.self ||
|
||||
result.last?.1 == ChatMessageGiveawayBubbleContentNode.self {
|
||||
result.append((firstMessage, ChatMessageReactionsFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform, neighborSpacing: .default)))
|
||||
result.append((firstMessage, ChatMessageReactionsFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .reactions, neighborSpacing: .default)))
|
||||
needReactions = false
|
||||
} else if result.last?.1 == ChatMessageCommentFooterContentNode.self {
|
||||
if result.count >= 2 {
|
||||
if result[result.count - 2].1 == ChatMessageWebpageBubbleContentNode.self ||
|
||||
result[result.count - 2].1 == ChatMessagePollBubbleContentNode.self ||
|
||||
if result[result.count - 2].1 == ChatMessagePollBubbleContentNode.self ||
|
||||
result[result.count - 2].1 == ChatMessageContactBubbleContentNode.self ||
|
||||
result[result.count - 2].1 == ChatMessageGiveawayBubbleContentNode.self {
|
||||
result.insert((firstMessage, ChatMessageReactionsFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform, neighborSpacing: .default)), at: result.count - 1)
|
||||
result.insert((firstMessage, ChatMessageReactionsFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .reactions, neighborSpacing: .default)), at: result.count - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -705,7 +703,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
}
|
||||
|
||||
if let singleUrl = accessibilityData.singleUrl {
|
||||
strongSelf.item?.controllerInteraction.openUrl(singleUrl, false, false, strongSelf.item?.content.firstMessage)
|
||||
strongSelf.item?.controllerInteraction.openUrl(singleUrl, false, false, strongSelf.item?.content.firstMessage, nil)
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -994,7 +992,18 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
recognizer.tapActionAtPoint = { [weak self] point in
|
||||
if let strongSelf = self {
|
||||
if let item = strongSelf.item, let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject {
|
||||
if case .link = info {
|
||||
if case let .link(link) = info {
|
||||
let options = Atomic<ChatControllerSubject.LinkOptions?>(value: nil)
|
||||
link.options.start(next: { value in
|
||||
let _ = options.swap(value)
|
||||
}).dispose()
|
||||
guard let options = options.with({ $0 }) else {
|
||||
return .fail
|
||||
}
|
||||
if !options.hasAlternativeLinks {
|
||||
return .fail
|
||||
}
|
||||
|
||||
for contentNode in strongSelf.contentNodes {
|
||||
let contentNodePoint = strongSelf.view.convert(point, to: contentNode.view)
|
||||
let tapAction = contentNode.tapActionAtPoint(contentNodePoint, gesture: .tap, isEstimating: true)
|
||||
|
|
@ -1110,6 +1119,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
}
|
||||
recognizer.highlight = { [weak self] point in
|
||||
if let strongSelf = self {
|
||||
if let replyInfoNode = strongSelf.replyInfoNode {
|
||||
var translatedPoint: CGPoint?
|
||||
let convertedNodeFrame = replyInfoNode.view.convert(replyInfoNode.bounds, to: strongSelf.view)
|
||||
if let point = point, convertedNodeFrame.insetBy(dx: -4.0, dy: -4.0).contains(point) {
|
||||
translatedPoint = strongSelf.view.convert(point, to: replyInfoNode.view)
|
||||
}
|
||||
replyInfoNode.updateTouchesAtPoint(translatedPoint)
|
||||
}
|
||||
for contentNode in strongSelf.contentNodes {
|
||||
var translatedPoint: CGPoint?
|
||||
let convertedNodeFrame = contentNode.view.convert(contentNode.bounds, to: strongSelf.view)
|
||||
|
|
@ -1240,7 +1257,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: isSelected)
|
||||
|
||||
let fontSize = floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)
|
||||
let nameFont = Font.medium(fontSize)
|
||||
let nameFont = Font.semibold(fontSize)
|
||||
|
||||
let inlineBotPrefixFont = Font.regular(fontSize)
|
||||
|
||||
|
|
@ -1709,8 +1726,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
let topPosition: ChatMessageBubbleRelativePosition
|
||||
let bottomPosition: ChatMessageBubbleRelativePosition
|
||||
|
||||
var topBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)
|
||||
var bottomBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)
|
||||
var topBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)
|
||||
var bottomBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)
|
||||
if index != 0 {
|
||||
topBubbleAttributes = contentPropertiesAndPrepareLayouts[index - 1].3
|
||||
}
|
||||
|
|
@ -1903,7 +1920,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
|
||||
let firstNodeTopPosition: ChatMessageBubbleRelativePosition
|
||||
if displayHeader {
|
||||
firstNodeTopPosition = .Neighbour(false, .freeform, .default)
|
||||
firstNodeTopPosition = .Neighbour(false, .header, .default)
|
||||
} else {
|
||||
firstNodeTopPosition = .None(topNodeMergeStatus)
|
||||
}
|
||||
|
|
@ -2027,7 +2044,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
let bubbleWidthInsets: CGFloat = mosaicRange == nil ? layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right : 0.0
|
||||
if authorNameString != nil || inlineBotNameString != nil {
|
||||
if headerSize.height.isZero {
|
||||
headerSize.height += 5.0
|
||||
headerSize.height += 7.0
|
||||
}
|
||||
|
||||
let inlineBotNameColor = messageTheme.accentTextColor
|
||||
|
|
@ -2194,9 +2211,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
|
||||
if !isInstantVideo, hasReply, (replyMessage != nil || replyForward != nil || replyStory != nil) {
|
||||
if headerSize.height.isZero {
|
||||
headerSize.height += 10.0
|
||||
headerSize.height += 11.0
|
||||
} else {
|
||||
headerSize.height += 1.0
|
||||
headerSize.height += 2.0
|
||||
}
|
||||
let sizeAndApply = replyInfoLayout(ChatMessageReplyInfoNode.Arguments(
|
||||
presentationData: item.presentationData,
|
||||
|
|
@ -2218,10 +2235,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
replyInfoOriginY = headerSize.height
|
||||
headerSize.width = max(headerSize.width, replyInfoSizeApply.0.width + bubbleWidthInsets)
|
||||
headerSize.height += replyInfoSizeApply.0.height + 7.0
|
||||
}
|
||||
|
||||
if !headerSize.height.isZero {
|
||||
headerSize.height -= 5.0
|
||||
|
||||
if !headerSize.height.isZero {
|
||||
headerSize.height -= 7.0
|
||||
}
|
||||
} else {
|
||||
if !headerSize.height.isZero {
|
||||
headerSize.height -= 5.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2353,7 +2374,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
if mosaicRange.upperBound - 1 == contentNodeCount - 1 {
|
||||
lastMosaicBottomPosition = lastNodeTopPosition
|
||||
} else {
|
||||
lastMosaicBottomPosition = .Neighbour(false, .freeform, .default)
|
||||
lastMosaicBottomPosition = .Neighbour(false, .text, .default)
|
||||
}
|
||||
|
||||
if position.contains(.bottom), case .Neighbour = lastMosaicBottomPosition {
|
||||
|
|
@ -2421,8 +2442,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
let topPosition: ChatMessageBubbleRelativePosition
|
||||
let bottomPosition: ChatMessageBubbleRelativePosition
|
||||
|
||||
var topBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)
|
||||
var bottomBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)
|
||||
var topBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)
|
||||
var bottomBubbleAttributes = BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)
|
||||
if i != 0 {
|
||||
topBubbleAttributes = contentPropertiesAndLayouts[i - 1].3
|
||||
}
|
||||
|
|
@ -2445,7 +2466,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
contentPosition = .linear(top: topPosition, bottom: bottomPosition)
|
||||
case .mosaic:
|
||||
assertionFailure()
|
||||
contentPosition = .linear(top: .Neighbour(false, .freeform, .default), bottom: .Neighbour(false, .freeform, .default))
|
||||
contentPosition = .linear(top: .Neighbour(false, .text, .default), bottom: .Neighbour(false, .text, .default))
|
||||
}
|
||||
let (contentNodeWidth, contentNodeFinalize) = contentNodeLayout(CGSize(width: maximumNodeWidth, height: CGFloat.greatestFiniteMagnitude), contentPosition)
|
||||
#if DEBUG
|
||||
|
|
@ -3267,6 +3288,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
contextSourceNode = strongSelf.contentContainers.first(where: { $0.contentMessageStableId == contentNodeMessage.stableId })?.sourceNode ?? strongSelf.mainContextSourceNode
|
||||
containerSupernode = strongSelf.contentContainers.first(where: { $0.contentMessageStableId == contentNodeMessage.stableId })?.sourceNode.contentNode ?? strongSelf.clippingNode
|
||||
}
|
||||
|
||||
#if DEBUG && false
|
||||
contentNode.layer.borderColor = UIColor(white: 0.0, alpha: 0.2).cgColor
|
||||
contentNode.layer.borderWidth = 1.0
|
||||
#endif
|
||||
|
||||
containerSupernode.addSubnode(contentNode)
|
||||
|
||||
contentNode.itemNode = strongSelf
|
||||
|
|
@ -3855,11 +3882,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
if let item = self.item {
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
return .optionalAction({
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId)
|
||||
return .action({
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text))
|
||||
})
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
return .optionalAction({
|
||||
return .action({
|
||||
item.controllerInteraction.navigateToStory(item.message, attribute.storyId)
|
||||
})
|
||||
}
|
||||
|
|
@ -3888,7 +3915,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
return
|
||||
}
|
||||
}
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId)
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId, NavigateToMessageParams(timestamp: nil, quote: nil))
|
||||
} else if let peer = forwardInfo.source ?? forwardInfo.author {
|
||||
item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
|
||||
} else if let _ = forwardInfo.authorSignature {
|
||||
|
|
@ -3937,9 +3964,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
return .action({
|
||||
})
|
||||
}
|
||||
case let .url(url, concealed):
|
||||
case let .url(url, concealed, activate):
|
||||
return .action({
|
||||
self.item?.controllerInteraction.openUrl(url, concealed, nil, self.item?.content.firstMessage)
|
||||
self.item?.controllerInteraction.openUrl(url, concealed, nil, self.item?.content.firstMessage, activate?())
|
||||
})
|
||||
case let .peerMention(peerId, _, openProfile):
|
||||
return .action({ [weak self] in
|
||||
|
|
@ -4052,6 +4079,21 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
if let threadInfoNode = self.threadInfoNode, self.item?.controllerInteraction.tapMessage == nil, threadInfoNode.frame.contains(location) {
|
||||
return .action({})
|
||||
}
|
||||
if let replyInfoNode = self.replyInfoNode, self.item?.controllerInteraction.tapMessage == nil, replyInfoNode.frame.contains(location) {
|
||||
if let item = self.item {
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
return .action({
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text))
|
||||
})
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
return .action({
|
||||
item.controllerInteraction.navigateToStory(item.message, attribute.storyId)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tapMessage: Message? = item.content.firstMessage
|
||||
var selectAll = true
|
||||
|
|
@ -4081,7 +4123,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
switch tapAction {
|
||||
case .none, .ignore:
|
||||
break
|
||||
case let .url(url, _):
|
||||
case let .url(url, _, _):
|
||||
return .action({
|
||||
item.controllerInteraction.longTap(.url(url), message)
|
||||
})
|
||||
|
|
@ -4436,6 +4478,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
}
|
||||
|
||||
var highlighted = false
|
||||
var highlightedQuote: String?
|
||||
|
||||
for contentNode in self.contentNodes {
|
||||
let _ = contentNode.updateHighlightedState(animated: animated)
|
||||
|
|
@ -4445,6 +4488,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
for (message, _) in item.content {
|
||||
if highlightedState.messageStableId == message.stableId {
|
||||
highlighted = true
|
||||
highlightedQuote = highlightedState.quote
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -4459,6 +4503,20 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
self.backgroundNode.setType(type: backgroundType, highlighted: highlighted, graphics: graphics, maskMode: self.backgroundMaskMode, hasWallpaper: hasWallpaper, transition: animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate, backgroundNode: item.controllerInteraction.presentationContext.backgroundNode)
|
||||
}
|
||||
}
|
||||
|
||||
if let highlightedQuote {
|
||||
for contentNode in self.contentNodes {
|
||||
if let contentNode = contentNode as? ChatMessageTextBubbleContentNode {
|
||||
contentNode.updateQuoteTextHighlightState(text: highlightedQuote, animated: animated)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for contentNode in self.contentNodes {
|
||||
if let contentNode = contentNode as? ChatMessageTextBubbleContentNode {
|
||||
contentNode.updateQuoteTextHighlightState(text: nil, animated: animated)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func shareButtonPressed() {
|
||||
|
|
@ -4468,7 +4526,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
} else if item.content.firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) {
|
||||
for attribute in item.content.firstMessage.attributes {
|
||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||
item.controllerInteraction.navigateToMessage(item.content.firstMessage.id, attribute.messageId)
|
||||
item.controllerInteraction.navigateToMessage(item.content.firstMessage.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: nil))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -246,30 +246,17 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
))
|
||||
}
|
||||
|
||||
let buttonImage: UIImage
|
||||
let buttonHighlightedImage: UIImage
|
||||
let titleColor: UIColor
|
||||
let titleHighlightedColor: UIColor
|
||||
let avatarPlaceholderColor: UIColor
|
||||
if incoming {
|
||||
buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonIncoming(item.presentationData.theme.theme)!
|
||||
buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIncoming(item.presentationData.theme.theme)!
|
||||
titleColor = item.presentationData.theme.theme.chat.message.incoming.accentTextColor
|
||||
|
||||
let bubbleColors = bubbleColorComponents(theme: item.presentationData.theme.theme, incoming: true, wallpaper: !item.presentationData.theme.wallpaper.isEmpty)
|
||||
titleHighlightedColor = bubbleColors.fill[0]
|
||||
avatarPlaceholderColor = item.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor
|
||||
} else {
|
||||
buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonOutgoing(item.presentationData.theme.theme)!
|
||||
buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonOutgoing(item.presentationData.theme.theme)!
|
||||
titleColor = item.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
|
||||
let bubbleColors = bubbleColorComponents(theme: item.presentationData.theme.theme, incoming: false, wallpaper: !item.presentationData.theme.wallpaper.isEmpty)
|
||||
titleHighlightedColor = bubbleColors.fill[0]
|
||||
avatarPlaceholderColor = item.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor
|
||||
}
|
||||
|
||||
let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, buttonImage, buttonHighlightedImage, nil, nil, false, item.presentationData.strings.Conversation_ViewContactDetails, titleColor, titleHighlightedColor, false)
|
||||
let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, nil, false, item.presentationData.strings.Conversation_ViewContactDetails, titleColor, false, true)
|
||||
|
||||
var maxContentWidth: CGFloat = avatarSize.width + 7.0
|
||||
if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue {
|
||||
|
|
@ -321,7 +308,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
|
||||
let _ = titleApply()
|
||||
let _ = textApply()
|
||||
let _ = buttonApply()
|
||||
let _ = buttonApply(animation)
|
||||
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: avatarFrame.maxX + 7.0, y: avatarFrame.minY + 1.0), size: titleLayout.size)
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: avatarFrame.maxX + 7.0, y: avatarFrame.minY + 20.0), size: textLayout.size)
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
public enum LayoutInput {
|
||||
case trailingContent(contentWidth: CGFloat, reactionSettings: TrailingReactionSettings?)
|
||||
case trailingContent(contentWidth: CGFloat?, reactionSettings: TrailingReactionSettings?)
|
||||
case standalone(reactionSettings: StandaloneReactionSettings?)
|
||||
|
||||
public var displayInlineReactions: Bool {
|
||||
|
|
@ -848,14 +848,20 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||
|
||||
if reactionButtonsSize.width.isZero {
|
||||
verticalReactionsInset = 0.0
|
||||
if contentWidth + layoutSize.width > arguments.constrainedSize.width {
|
||||
if let contentWidth {
|
||||
if contentWidth + layoutSize.width > arguments.constrainedSize.width {
|
||||
resultingWidth = layoutSize.width
|
||||
verticalInset = 0.0
|
||||
resultingHeight = layoutSize.height + verticalInset
|
||||
} else {
|
||||
resultingWidth = contentWidth + layoutSize.width
|
||||
verticalInset = -layoutSize.height
|
||||
resultingHeight = 0.0
|
||||
}
|
||||
} else {
|
||||
resultingWidth = layoutSize.width
|
||||
verticalInset = 0.0
|
||||
resultingHeight = layoutSize.height + verticalInset
|
||||
} else {
|
||||
resultingWidth = contentWidth + layoutSize.width
|
||||
verticalInset = -layoutSize.height
|
||||
resultingHeight = 0.0
|
||||
}
|
||||
} else {
|
||||
var additionalVerticalInset: CGFloat = 0.0
|
||||
|
|
|
|||
|
|
@ -513,7 +513,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
if let (attributeText, fullText) = self.labelNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return .url(url: url, concealed: concealed)
|
||||
return .url(url: url, concealed: concealed, activate: nil)
|
||||
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
return .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)
|
||||
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
|
|
|
|||
|
|
@ -380,27 +380,14 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
|||
))
|
||||
}
|
||||
|
||||
let buttonImage: UIImage
|
||||
let buttonHighlightedImage: UIImage
|
||||
let titleColor: UIColor
|
||||
let titleHighlightedColor: UIColor
|
||||
if incoming {
|
||||
buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonIncoming(item.presentationData.theme.theme)!
|
||||
buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIncoming(item.presentationData.theme.theme)!
|
||||
titleColor = item.presentationData.theme.theme.chat.message.incoming.accentTextColor
|
||||
|
||||
let bubbleColors = bubbleColorComponents(theme: item.presentationData.theme.theme, incoming: true, wallpaper: !item.presentationData.theme.wallpaper.isEmpty)
|
||||
titleHighlightedColor = bubbleColors.fill[0]
|
||||
} else {
|
||||
buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonOutgoing(item.presentationData.theme.theme)!
|
||||
buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonOutgoing(item.presentationData.theme.theme)!
|
||||
titleColor = item.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
|
||||
let bubbleColors = bubbleColorComponents(theme: item.presentationData.theme.theme, incoming: false, wallpaper: !item.presentationData.theme.wallpaper.isEmpty)
|
||||
titleHighlightedColor = bubbleColors.fill[0]
|
||||
}
|
||||
|
||||
let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, buttonImage, buttonHighlightedImage, nil, nil, false, "LEARN MORE", titleColor, titleHighlightedColor, false)
|
||||
let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, nil, false, "LEARN MORE", titleColor, false, true)
|
||||
|
||||
let months = giveaway?.months ?? 0
|
||||
let animationName: String
|
||||
|
|
@ -476,7 +463,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
|
|||
let _ = dateTitleApply()
|
||||
let _ = dateTextApply()
|
||||
let _ = channelButtonApply()
|
||||
let _ = buttonApply()
|
||||
let _ = buttonApply(animation)
|
||||
|
||||
let smallSpacing: CGFloat = 2.0
|
||||
let largeSpacing: CGFloat = 14.0
|
||||
|
|
|
|||
|
|
@ -960,7 +960,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco
|
|||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
return .optionalAction({
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId)
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -979,7 +979,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco
|
|||
return
|
||||
}
|
||||
}
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId)
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId, NavigateToMessageParams(timestamp: nil, quote: nil))
|
||||
} else if let peer = forwardInfo.source ?? forwardInfo.author {
|
||||
item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
|
||||
} else if let _ = forwardInfo.authorSignature {
|
||||
|
|
@ -1026,7 +1026,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco
|
|||
} else if item.content.firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) {
|
||||
for attribute in item.content.firstMessage.attributes {
|
||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||
item.controllerInteraction.navigateToMessage(item.content.firstMessage.id, attribute.messageId)
|
||||
item.controllerInteraction.navigateToMessage(item.content.firstMessage.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: nil))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode",
|
||||
"//submodules/TelegramUI/Components/Chat/InstantVideoRadialStatusNode",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatInstantVideoMessageDurationNode",
|
||||
"//submodules/TelegramUI/Components/ChatControllerInteraction",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import ChatMessageBubbleContentNode
|
|||
import ChatMessageReplyInfoNode
|
||||
import InstantVideoRadialStatusNode
|
||||
import ChatInstantVideoMessageDurationNode
|
||||
import ChatControllerInteraction
|
||||
|
||||
public struct ChatMessageInstantVideoItemLayoutResult {
|
||||
public let contentSize: CGSize
|
||||
|
|
@ -1288,7 +1289,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
|||
if let item = self.item {
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId)
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text))
|
||||
return
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
item.controllerInteraction.navigateToStory(item.message, attribute.storyId)
|
||||
|
|
@ -1309,7 +1310,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
|||
return
|
||||
}
|
||||
}
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId)
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId, NavigateToMessageParams(timestamp: nil, quote: nil))
|
||||
return
|
||||
} else if let peer = forwardInfo.source ?? forwardInfo.author {
|
||||
item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ public struct ChatMessageItemLayoutConstants {
|
|||
}
|
||||
|
||||
public static var compact: ChatMessageItemLayoutConstants {
|
||||
let bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 3.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 0.0, maximumWidthFill: ChatMessageItemWidthFill(compactInset: 36.0, compactWidthBoundary: 500.0, freeMaximumFillFactor: 0.85), minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 0.0, left: 5.0, bottom: 0.0, right: 0.0), borderInset: UIScreenPixel, strokeInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0))
|
||||
let bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 3.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 0.0, maximumWidthFill: ChatMessageItemWidthFill(compactInset: 36.0, compactWidthBoundary: 500.0, freeMaximumFillFactor: 0.85), minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 0.0), borderInset: UIScreenPixel, strokeInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0))
|
||||
let text = ChatMessageItemTextLayoutConstants(bubbleInsets: UIEdgeInsets(top: 6.0 + UIScreenPixel, left: 11.0, bottom: 6.0 - UIScreenPixel, right: 11.0))
|
||||
let image = ChatMessageItemImageLayoutConstants(bubbleInsets: UIEdgeInsets(top: 2.0, left: 2.0, bottom: 2.0, right: 2.0), statusInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 6.0, right: 6.0), defaultCornerRadius: 16.0, mergedCornerRadius: 8.0, contentMergedCornerRadius: 0.0, maxDimensions: CGSize(width: 300.0, height: 380.0), minDimensions: CGSize(width: 170.0, height: 74.0))
|
||||
let video = ChatMessageItemVideoLayoutConstants(maxHorizontalHeight: 250.0, maxVerticalHeight: 360.0)
|
||||
|
|
@ -146,7 +146,7 @@ public struct ChatMessageItemLayoutConstants {
|
|||
}
|
||||
|
||||
public static var regular: ChatMessageItemLayoutConstants {
|
||||
let bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 3.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 0.0, maximumWidthFill: ChatMessageItemWidthFill(compactInset: 36.0, compactWidthBoundary: 500.0, freeMaximumFillFactor: 0.65), minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 0.0, left: 5.0, bottom: 0.0, right: 0.0), borderInset: UIScreenPixel, strokeInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0))
|
||||
let bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 3.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 0.0, maximumWidthFill: ChatMessageItemWidthFill(compactInset: 36.0, compactWidthBoundary: 500.0, freeMaximumFillFactor: 0.65), minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 0.0), borderInset: UIScreenPixel, strokeInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0))
|
||||
let text = ChatMessageItemTextLayoutConstants(bubbleInsets: UIEdgeInsets(top: 6.0 + UIScreenPixel, left: 10.0, bottom: 6.0 - UIScreenPixel, right: 10.0))
|
||||
let image = ChatMessageItemImageLayoutConstants(bubbleInsets: UIEdgeInsets(top: 2.0, left: 2.0, bottom: 2.0, right: 2.0), statusInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 6.0, right: 6.0), defaultCornerRadius: 16.0, mergedCornerRadius: 8.0, contentMergedCornerRadius: 5.0, maxDimensions: CGSize(width: 440.0, height: 440.0), minDimensions: CGSize(width: 170.0, height: 74.0))
|
||||
let video = ChatMessageItemVideoLayoutConstants(maxHorizontalHeight: 250.0, maxVerticalHeight: 360.0)
|
||||
|
|
|
|||
|
|
@ -504,7 +504,17 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
|||
async {
|
||||
let (top, bottom, dateAtBottom) = self.mergedWithItems(top: previousItem, bottom: nextItem)
|
||||
|
||||
let (layout, apply) = nodeLayout(self, params, top, bottom, dateAtBottom && !self.disableDate)
|
||||
var disableDate = self.disableDate
|
||||
if let subject = self.associatedData.subject, case let .messageOptions(_, _, info) = subject {
|
||||
switch info {
|
||||
case .reply, .link:
|
||||
disableDate = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let (layout, apply) = nodeLayout(self, params, top, bottom, dateAtBottom && !disableDate)
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { info in
|
||||
apply(animation, info, false)
|
||||
|
|
|
|||
|
|
@ -753,7 +753,7 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
|
|||
if url.hasPrefix("tg://") {
|
||||
concealed = false
|
||||
}
|
||||
item.controllerInteraction.openUrl(url, concealed, nil, nil)
|
||||
item.controllerInteraction.openUrl(url, concealed, nil, nil, nil)
|
||||
case .requestMap:
|
||||
item.controllerInteraction.shareCurrentLocation()
|
||||
case .requestPhone:
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
if activeLiveBroadcastingTimeout != nil || selectedMedia?.venue != nil {
|
||||
var relativePosition = position
|
||||
if case let .linear(top, _) = position {
|
||||
relativePosition = .linear(top: top, bottom: .Neighbour(false, .freeform, .default))
|
||||
relativePosition = .linear(top: top, bottom: .Neighbour(false, .text, .default))
|
||||
}
|
||||
imageCorners = chatMessageBubbleImageContentCorners(relativeContentPosition: relativePosition, normalRadius: layoutConstants.image.defaultCornerRadius, mergedRadius: layoutConstants.image.mergedCornerRadius, mergedWithAnotherContentRadius: layoutConstants.image.contentMergedCornerRadius, layoutConstants: layoutConstants, chatPresentationData: item.presentationData)
|
||||
|
||||
|
|
|
|||
|
|
@ -1608,7 +1608,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
if let (attributeText, fullText) = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return .url(url: url, concealed: concealed)
|
||||
return .url(url: url, concealed: concealed, activate: nil)
|
||||
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
return .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)
|
||||
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
|
|
|
|||
|
|
@ -131,11 +131,11 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||
|
||||
return { arguments in
|
||||
let fontSize = floor(arguments.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)
|
||||
let titleFont = Font.medium(fontSize)
|
||||
let titleFont = Font.semibold(fontSize)
|
||||
let textFont = Font.regular(fontSize)
|
||||
|
||||
var titleString: String
|
||||
let textString: NSAttributedString
|
||||
var textString: NSAttributedString
|
||||
let isMedia: Bool
|
||||
let isText: Bool
|
||||
var isExpiredStory: Bool = false
|
||||
|
|
@ -165,12 +165,29 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||
isMedia = isMediaValue
|
||||
isText = isTextValue
|
||||
} else if let replyForward = arguments.replyForward {
|
||||
titleString = replyForward.authorName ?? " "
|
||||
if let replyAuthorId = replyForward.peerId, let replyAuthor = arguments.parentMessage.peers[replyAuthorId] {
|
||||
titleString = EnginePeer(replyAuthor).displayTitle(strings: arguments.strings, displayOrder: arguments.presentationData.nameDisplayOrder)
|
||||
} else {
|
||||
titleString = replyForward.authorName ?? " "
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
textString = NSAttributedString(string: replyForward.quote?.text ?? "Message")
|
||||
isMedia = false
|
||||
isText = true
|
||||
if let media = replyForward.quote?.media {
|
||||
if let text = replyForward.quote?.text, !text.isEmpty {
|
||||
} else {
|
||||
if let contentKind = mediaContentKind(EngineMedia(media), message: nil, strings: arguments.strings, nameDisplayOrder: arguments.presentationData.nameDisplayOrder, dateTimeFormat: arguments.presentationData.dateTimeFormat, accountPeerId: arguments.context.account.peerId) {
|
||||
let (string, _) = stringForMediaKind(contentKind, strings: arguments.strings)
|
||||
textString = string
|
||||
} else {
|
||||
textString = NSAttributedString(string: "Message")
|
||||
}
|
||||
}
|
||||
isMedia = true
|
||||
} else {
|
||||
isMedia = false
|
||||
}
|
||||
isText = replyForward.quote?.text != nil && replyForward.quote?.text != ""
|
||||
} else if let story = arguments.story {
|
||||
if let authorPeer = arguments.parentMessage.peers[story.peerId] {
|
||||
titleString = EnginePeer(authorPeer).displayTitle(strings: arguments.strings, displayOrder: arguments.presentationData.nameDisplayOrder)
|
||||
|
|
@ -310,7 +327,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||
} else {
|
||||
messageText = NSAttributedString(string: text, font: textFont, textColor: textColor)
|
||||
}
|
||||
} else if let replyForward = arguments.replyForward, let quote = replyForward.quote {
|
||||
} else if isText, let replyForward = arguments.replyForward, let quote = replyForward.quote {
|
||||
let entities = quote.entities.filter { entity in
|
||||
if case .Strikethrough = entity.type {
|
||||
return true
|
||||
|
|
@ -376,6 +393,24 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if let replyForward = arguments.replyForward, let media = replyForward.quote?.media {
|
||||
if let image = media as? TelegramMediaImage {
|
||||
updatedMediaReference = .message(message: MessageReference(arguments.parentMessage), media: image)
|
||||
if let representation = largestRepresentationForPhoto(image) {
|
||||
imageDimensions = representation.dimensions.cgSize
|
||||
}
|
||||
} else if let file = media as? TelegramMediaFile, file.isVideo && !file.isVideoSticker {
|
||||
updatedMediaReference = .message(message: MessageReference(arguments.parentMessage), media: file)
|
||||
|
||||
if let dimensions = file.dimensions {
|
||||
imageDimensions = dimensions.cgSize
|
||||
} else if let representation = largestImageRepresentation(file.previewRepresentations), !file.isSticker {
|
||||
imageDimensions = representation.dimensions.cgSize
|
||||
}
|
||||
if file.isInstantVideo {
|
||||
hasRoundImage = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var imageTextInset: CGFloat = 0.0
|
||||
|
|
@ -608,6 +643,10 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||
node.backgroundView.tintColor = mainColor
|
||||
node.backgroundView.frame = backgroundFrame
|
||||
|
||||
#if DEBUG && false
|
||||
node.backgroundColor = .blue
|
||||
#endif
|
||||
|
||||
if arguments.quote != nil || arguments.replyForward?.quote != nil {
|
||||
let quoteIconView: UIImageView
|
||||
if let current = node.quoteIconView {
|
||||
|
|
@ -632,6 +671,17 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
public func updateTouchesAtPoint(_ point: CGPoint?) {
|
||||
var isHighlighted = false
|
||||
if point != nil {
|
||||
isHighlighted = true
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: isHighlighted ? 0.1 : 0.2, curve: .easeInOut)
|
||||
let scale: CGFloat = isHighlighted ? ((self.bounds.width - 5.0) / self.bounds.width) : 1.0
|
||||
transition.updateSublayerTransformScale(node: self, scale: scale)
|
||||
}
|
||||
|
||||
public func animateFromInputPanel(sourceReplyPanel: TransitionReplyPanel, unclippedTransitionNode: ASDisplayNode? = nil, localRect: CGRect, transition: CombinedTransition) -> CGPoint {
|
||||
let sourceParentNode = ASDisplayNode()
|
||||
|
|
|
|||
|
|
@ -1334,7 +1334,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
return .optionalAction({
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId)
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text))
|
||||
})
|
||||
} else if let attribute = attribute as? ReplyStoryAttribute {
|
||||
return .optionalAction({
|
||||
|
|
@ -1357,7 +1357,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||
return
|
||||
}
|
||||
}
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId)
|
||||
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId, NavigateToMessageParams(timestamp: nil, quote: nil))
|
||||
} else if let peer = forwardInfo.source ?? forwardInfo.author {
|
||||
item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default)
|
||||
} else if let _ = forwardInfo.authorSignature {
|
||||
|
|
@ -1411,7 +1411,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||
} else if item.content.firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) {
|
||||
for attribute in item.content.firstMessage.attributes {
|
||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||
item.controllerInteraction.navigateToMessage(item.content.firstMessage.id, attribute.messageId)
|
||||
item.controllerInteraction.navigateToMessage(item.content.firstMessage.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: nil))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/Chat/ShimmeringLinkNode",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon",
|
||||
"//submodules/TelegramUI/Components/Chat/MessageQuoteComponent",
|
||||
"//submodules/TelegramUI/Components/RichTextView",
|
||||
"//submodules/TelegramUI/Components/TextLoadingEffect",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import ChatMessageDateAndStatusNode
|
|||
import ChatMessageBubbleContentNode
|
||||
import ShimmeringLinkNode
|
||||
import ChatMessageItemCommon
|
||||
import RichTextView
|
||||
import TextLoadingEffect
|
||||
|
||||
private final class CachedChatMessageText {
|
||||
let text: String
|
||||
|
|
@ -72,6 +72,12 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
private var linkPreviewOptionsDisposable: Disposable?
|
||||
private var linkPreviewHighlightingNodes: [LinkHighlightingNode] = []
|
||||
|
||||
private var quoteHighlightingNodes: [LinkHighlightingNode] = []
|
||||
|
||||
private var linkProgressRange: NSRange?
|
||||
private var linkProgressView: TextLoadingEffectView?
|
||||
private var linkProgressDisposable: Disposable?
|
||||
|
||||
override public var visibility: ListViewItemNodeVisibility {
|
||||
didSet {
|
||||
if oldValue != self.visibility {
|
||||
|
|
@ -107,7 +113,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
self.addSubnode(self.textAccessibilityOverlayNode)
|
||||
|
||||
self.textAccessibilityOverlayNode.openUrl = { [weak self] url in
|
||||
self?.item?.controllerInteraction.openUrl(url, false, false, nil)
|
||||
self?.item?.controllerInteraction.openUrl(url, false, false, nil, nil)
|
||||
}
|
||||
|
||||
self.statusNode.reactionSelected = { [weak self] value in
|
||||
|
|
@ -133,6 +139,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
|
||||
deinit {
|
||||
self.linkPreviewOptionsDisposable?.dispose()
|
||||
self.linkProgressDisposable?.dispose()
|
||||
}
|
||||
|
||||
override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
|
||||
|
|
@ -146,6 +153,31 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||
|
||||
return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in
|
||||
var topInset: CGFloat = 0.0
|
||||
var bottomInset: CGFloat = 0.0
|
||||
if case let .linear(top, bottom) = position {
|
||||
switch top {
|
||||
case .None:
|
||||
topInset = layoutConstants.text.bubbleInsets.top
|
||||
case let .Neighbour(_, topType, _):
|
||||
switch topType {
|
||||
case .text:
|
||||
topInset = layoutConstants.text.bubbleInsets.top - 2.0
|
||||
case .header, .footer, .media, .reactions:
|
||||
topInset = layoutConstants.text.bubbleInsets.top
|
||||
}
|
||||
default:
|
||||
topInset = layoutConstants.text.bubbleInsets.top
|
||||
}
|
||||
|
||||
switch bottom {
|
||||
case .None:
|
||||
bottomInset = layoutConstants.text.bubbleInsets.bottom
|
||||
default:
|
||||
bottomInset = layoutConstants.text.bubbleInsets.bottom - 3.0
|
||||
}
|
||||
}
|
||||
|
||||
let message = item.message
|
||||
|
||||
let incoming: Bool
|
||||
|
|
@ -353,7 +385,12 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
let textFont = item.presentationData.messageFont
|
||||
|
||||
if let entities = entities {
|
||||
attributedText = stringWithAppliedEntities(rawText, entities: entities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseQuoteTintColor: messageTheme.accentTextColor, baseFont: textFont, linkFont: textFont, boldFont: item.presentationData.messageBoldFont, italicFont: item.presentationData.messageItalicFont, boldItalicFont: item.presentationData.messageBoldItalicFont, fixedFont: item.presentationData.messageFixedFont, blockQuoteFont: item.presentationData.messageBlockQuoteFont, message: item.message, adjustQuoteFontSize: true)
|
||||
var underlineLinks = true
|
||||
if !messageTheme.primaryTextColor.isEqual(messageTheme.linkTextColor) {
|
||||
underlineLinks = false
|
||||
}
|
||||
|
||||
attributedText = stringWithAppliedEntities(rawText, entities: entities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseQuoteTintColor: messageTheme.accentTextColor, baseFont: textFont, linkFont: textFont, boldFont: item.presentationData.messageBoldFont, italicFont: item.presentationData.messageItalicFont, boldItalicFont: item.presentationData.messageBoldItalicFont, fixedFont: item.presentationData.messageFixedFont, blockQuoteFont: item.presentationData.messageBlockQuoteFont, underlineLinks: underlineLinks, message: item.message, adjustQuoteFontSize: true)
|
||||
} else if !rawText.isEmpty {
|
||||
attributedText = NSAttributedString(string: rawText, font: textFont, textColor: messageTheme.primaryTextColor)
|
||||
} else {
|
||||
|
|
@ -436,8 +473,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
var textFrame = CGRect(origin: CGPoint(x: -textInsets.left, y: -textInsets.top), size: textLayout.size)
|
||||
var textFrameWithoutInsets = CGRect(origin: CGPoint(x: textFrame.origin.x + textInsets.left, y: textFrame.origin.y + textInsets.top), size: CGSize(width: textFrame.width - textInsets.left - textInsets.right, height: textFrame.height - textInsets.top - textInsets.bottom))
|
||||
|
||||
textFrame = textFrame.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
|
||||
textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
|
||||
textFrame = textFrame.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: topInset)
|
||||
textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: topInset)
|
||||
|
||||
var suggestedBoundingWidth: CGFloat = textFrameWithoutInsets.width
|
||||
if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue {
|
||||
|
|
@ -457,7 +494,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
}
|
||||
|
||||
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
|
||||
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom
|
||||
|
||||
boundingSize.height += topInset + bottomInset
|
||||
|
||||
return (boundingSize, { [weak self] animation, synchronousLoads, _ in
|
||||
if let strongSelf = self {
|
||||
|
|
@ -605,11 +643,15 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
return
|
||||
}
|
||||
|
||||
strongSelf.updateLinkPreviewTextHighlightState(text: options.url)
|
||||
if options.hasAlternativeLinks {
|
||||
strongSelf.updateLinkPreviewTextHighlightState(text: options.url)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.updateLinkProgressState()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -646,10 +688,38 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
return .none
|
||||
} else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
var concealed = true
|
||||
if let (attributeText, fullText) = self.textNode.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
var urlRange: NSRange?
|
||||
if let (attributeText, fullText, urlRangeValue) = self.textNode.textNode.attributeSubstringWithRange(name: TelegramTextAttributes.URL, index: index) {
|
||||
urlRange = urlRangeValue
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
return .url(url: url, concealed: concealed)
|
||||
return .url(url: url, concealed: concealed, activate: { [weak self] in
|
||||
guard let self else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let promise = Promise<Bool>()
|
||||
|
||||
self.linkProgressDisposable?.dispose()
|
||||
|
||||
if self.linkProgressRange != nil {
|
||||
self.linkProgressRange = nil
|
||||
self.updateLinkProgressState()
|
||||
}
|
||||
|
||||
self.linkProgressDisposable = (promise.get() |> deliverOnMainQueue).startStrict(next: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let updatedRange: NSRange? = value ? urlRange : nil
|
||||
if self.linkProgressRange != updatedRange {
|
||||
self.linkProgressRange = updatedRange
|
||||
self.updateLinkProgressState()
|
||||
}
|
||||
})
|
||||
|
||||
return promise
|
||||
})
|
||||
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
return .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)
|
||||
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
|
|
@ -811,6 +881,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
|
||||
var rectsSet: [[CGRect]] = []
|
||||
if let text = text, !text.isEmpty, let cachedLayout = self.textNode.textNode.cachedLayout, let string = cachedLayout.attributedString?.string {
|
||||
let nsString = string as NSString
|
||||
|
|
@ -827,7 +898,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
if i < self.linkPreviewHighlightingNodes.count {
|
||||
textHighlightNode = self.linkPreviewHighlightingNodes[i]
|
||||
} else {
|
||||
textHighlightNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor)
|
||||
textHighlightNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor.withMultipliedAlpha(0.5) : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor.withMultipliedAlpha(0.5))
|
||||
self.linkPreviewHighlightingNodes.append(textHighlightNode)
|
||||
self.insertSubnode(textHighlightNode, belowSubnode: self.textNode.textNode)
|
||||
}
|
||||
|
|
@ -840,6 +911,77 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
}
|
||||
}
|
||||
|
||||
private func updateLinkProgressState() {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
|
||||
let range: NSRange = self.linkProgressRange ?? NSRange(location: NSNotFound, length: 0)
|
||||
if range.location != NSNotFound {
|
||||
let linkProgressView: TextLoadingEffectView
|
||||
if let current = self.linkProgressView {
|
||||
linkProgressView = current
|
||||
} else {
|
||||
linkProgressView = TextLoadingEffectView(frame: CGRect())
|
||||
self.linkProgressView = linkProgressView
|
||||
self.view.addSubview(linkProgressView)
|
||||
}
|
||||
linkProgressView.frame = self.textNode.textNode.frame
|
||||
|
||||
let progressColor: UIColor = item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor
|
||||
|
||||
linkProgressView.update(color: progressColor, textNode: self.textNode.textNode, range: range)
|
||||
} else {
|
||||
if let linkProgressView = self.linkProgressView {
|
||||
self.linkProgressView = nil
|
||||
linkProgressView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak linkProgressView] _ in
|
||||
linkProgressView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateQuoteTextHighlightState(text: String?, animated: Bool) {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
var rectsSet: [[CGRect]] = []
|
||||
if let text = text, !text.isEmpty, let cachedLayout = self.textNode.textNode.cachedLayout, let string = cachedLayout.attributedString?.string {
|
||||
let nsString = string as NSString
|
||||
let range = nsString.range(of: text)
|
||||
if range.location != NSNotFound {
|
||||
if let rects = cachedLayout.rangeRects(in: range)?.rects, !rects.isEmpty {
|
||||
rectsSet = [rects]
|
||||
}
|
||||
}
|
||||
}
|
||||
for i in 0 ..< rectsSet.count {
|
||||
let rects = rectsSet[i]
|
||||
let textHighlightNode: LinkHighlightingNode
|
||||
if i < self.quoteHighlightingNodes.count {
|
||||
textHighlightNode = self.quoteHighlightingNodes[i]
|
||||
} else {
|
||||
textHighlightNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor)
|
||||
self.quoteHighlightingNodes.append(textHighlightNode)
|
||||
self.insertSubnode(textHighlightNode, belowSubnode: self.textNode.textNode)
|
||||
}
|
||||
textHighlightNode.frame = self.textNode.textNode.frame
|
||||
textHighlightNode.updateRects(rects)
|
||||
}
|
||||
for i in (rectsSet.count ..< self.quoteHighlightingNodes.count).reversed() {
|
||||
let node = self.quoteHighlightingNodes[i]
|
||||
self.quoteHighlightingNodes.remove(at: i)
|
||||
|
||||
if animated {
|
||||
node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in
|
||||
node?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
node.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public func willUpdateIsExtractedToContextPreview(_ value: Bool) {
|
||||
if !value {
|
||||
if let textSelectionNode = self.textSelectionNode {
|
||||
|
|
|
|||
|
|
@ -42,29 +42,18 @@ public final class ChatMessageUnsupportedBubbleContentNode: ChatMessageBubbleCon
|
|||
let presentationData = item.presentationData
|
||||
let insets = UIEdgeInsets(top: 0.0, left: 12.0, bottom: 9.0, right: 12.0)
|
||||
|
||||
let buttonImage: UIImage
|
||||
let buttonHighlightedImage: UIImage
|
||||
let titleColor: UIColor
|
||||
let titleHighlightedColor: UIColor
|
||||
if incoming {
|
||||
buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonIncoming(presentationData.theme.theme)!
|
||||
buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIncoming(presentationData.theme.theme)!
|
||||
titleColor = presentationData.theme.theme.chat.message.incoming.accentTextColor
|
||||
let bubbleColor = bubbleColorComponents(theme: presentationData.theme.theme, incoming: true, wallpaper: !presentationData.theme.wallpaper.isEmpty)
|
||||
titleHighlightedColor = bubbleColor.fill[0]
|
||||
} else {
|
||||
buttonImage = PresentationResourcesChat.chatMessageAttachedContentButtonOutgoing(presentationData.theme.theme)!
|
||||
buttonHighlightedImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonOutgoing(presentationData.theme.theme)!
|
||||
titleColor = presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
let bubbleColor = bubbleColorComponents(theme: presentationData.theme.theme, incoming: false, wallpaper: !presentationData.theme.wallpaper.isEmpty)
|
||||
titleHighlightedColor = bubbleColor.fill[0]
|
||||
}
|
||||
let (buttonWidth, continueActionButtonLayout) = makeButtonLayout(constrainedSize.width, buttonImage, buttonHighlightedImage, nil, nil, false, presentationData.strings.Conversation_UpdateTelegram, titleColor, titleHighlightedColor, false)
|
||||
let (buttonWidth, continueActionButtonLayout) = makeButtonLayout(constrainedSize.width, nil, false, presentationData.strings.Conversation_UpdateTelegram, titleColor, false, true)
|
||||
|
||||
let initialWidth = buttonWidth + insets.left + insets.right
|
||||
|
||||
return (initialWidth, { boundingWidth in
|
||||
var actionButtonSizeAndApply: ((CGSize, () -> ChatMessageAttachedContentButtonNode))?
|
||||
var actionButtonSizeAndApply: ((CGSize, (ListViewItemUpdateAnimation) -> ChatMessageAttachedContentButtonNode))?
|
||||
let refinedButtonWidth = max(boundingWidth - insets.left - insets.right, buttonWidth)
|
||||
|
||||
let (size, apply) = continueActionButtonLayout(refinedButtonWidth, 33.0)
|
||||
|
|
@ -76,7 +65,7 @@ public final class ChatMessageUnsupportedBubbleContentNode: ChatMessageBubbleCon
|
|||
strongSelf.item = item
|
||||
|
||||
if let (size, apply) = actionButtonSizeAndApply {
|
||||
_ = apply()
|
||||
_ = apply(animation)
|
||||
strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: insets.left, y: 0.0), size: size)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/WallpaperPreviewMedia",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatHistoryEntry",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
|||
self.contentNode.openMedia = { [weak self] mode in
|
||||
if let strongSelf = self, let item = strongSelf.item {
|
||||
if let webPage = strongSelf.webPage, case let .Loaded(content) = webPage.content {
|
||||
if let _ = content.image, let _ = content.instantPage {
|
||||
if let _ = content.instantPage {
|
||||
if instantPageType(of: content) != .album {
|
||||
item.controllerInteraction.openInstantPage(item.message, item.associatedData)
|
||||
return
|
||||
|
|
@ -86,7 +86,11 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
|||
} else if webpage.instantPage != nil {
|
||||
strongSelf.contentNode.openMedia?(.default)
|
||||
} else {
|
||||
item.controllerInteraction.openUrl(webpage.url, false, nil, nil)
|
||||
var isConcealed = true
|
||||
if item.message.text.contains(webpage.url) {
|
||||
isConcealed = false
|
||||
}
|
||||
item.controllerInteraction.openUrl(webpage.url, isConcealed, nil, nil, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -97,6 +101,16 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
|||
let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false)
|
||||
}
|
||||
}
|
||||
self.contentNode.defaultContentAction = { [weak self] in
|
||||
guard let self, let item = self.item, let webPage = self.webPage, case let .Loaded(content) = webPage.content else {
|
||||
return .none
|
||||
}
|
||||
var isConcealed = true
|
||||
if item.message.text.contains(content.url) {
|
||||
isConcealed = false
|
||||
}
|
||||
return .url(url: content.url, concealed: isConcealed, activate: nil)
|
||||
}
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
|
|
@ -104,7 +118,8 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
|||
}
|
||||
|
||||
override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
|
||||
let contentNodeLayout = self.contentNode.asyncLayout()
|
||||
let currentWebpage = self.webPage
|
||||
let currentContentNodeLayout = self.contentNode.asyncLayout()
|
||||
|
||||
return { item, layoutConstants, preparePosition, _, constrainedSize, _ in
|
||||
var webPage: TelegramMediaWebpage?
|
||||
|
|
@ -119,6 +134,16 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
|||
}
|
||||
}
|
||||
|
||||
var updatedContentNode: ChatMessageAttachedContentNode?
|
||||
let contentNodeLayout: ChatMessageAttachedContentNode.AsyncLayout
|
||||
if currentWebpage == nil || currentWebpage?.webpageId == webPage?.id {
|
||||
contentNodeLayout = currentContentNodeLayout
|
||||
} else {
|
||||
let updatedContentNodeValue = ChatMessageAttachedContentNode()
|
||||
updatedContentNode = updatedContentNodeValue
|
||||
contentNodeLayout = updatedContentNodeValue.asyncLayout()
|
||||
}
|
||||
|
||||
var title: String?
|
||||
var subtitle: NSAttributedString?
|
||||
var text: String?
|
||||
|
|
@ -353,11 +378,15 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
|||
}
|
||||
}
|
||||
|
||||
if let largeMedia = webpage.displayOptions.largeMedia {
|
||||
if largeMedia {
|
||||
mediaAndFlags?.1.remove(.preferMediaInline)
|
||||
} else {
|
||||
mediaAndFlags?.1.insert(.preferMediaInline)
|
||||
if let webPageContent, let isMediaLargeByDefault = webPageContent.isMediaLargeByDefault, !isMediaLargeByDefault {
|
||||
mediaAndFlags?.1.insert(.preferMediaInline)
|
||||
} else if let attribute = item.message.attributes.first(where: { $0 is WebpagePreviewMessageAttribute }) as? WebpagePreviewMessageAttribute {
|
||||
if let forceLargeMedia = attribute.forceLargeMedia {
|
||||
if forceLargeMedia {
|
||||
mediaAndFlags?.1.remove(.preferMediaInline)
|
||||
} else {
|
||||
mediaAndFlags?.1.insert(.preferMediaInline)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let adAttribute = item.message.adAttribute {
|
||||
|
|
@ -410,13 +439,39 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
|||
let (size, apply) = finalizeLayout(boundingWidth)
|
||||
|
||||
return (size, { [weak self] animation, synchronousLoads, applyInfo in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
strongSelf.webPage = webPage
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.item = item
|
||||
self.webPage = webPage
|
||||
|
||||
if let updatedContentNode {
|
||||
let previousPosition = self.contentNode.position
|
||||
let updatedPosition = CGPoint(x: size.width * 0.5, y: size.height * 0.5)
|
||||
|
||||
do {
|
||||
//animation.animator.updateScale(layer: self.contentNode.layer, scale: 0.9, completion: nil)
|
||||
animation.animator.updatePosition(layer: self.contentNode.layer, position: updatedPosition, completion: nil)
|
||||
animation.animator.updateAlpha(layer: self.contentNode.layer, alpha: 0.0, completion: { [weak contentNode] _ in
|
||||
contentNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
|
||||
self.contentNode = updatedContentNode
|
||||
self.addSubnode(updatedContentNode)
|
||||
|
||||
do {
|
||||
apply(.None, synchronousLoads, applyInfo)
|
||||
self.contentNode.frame = size.centered(around: previousPosition)
|
||||
|
||||
//animation.animator.animateScale(layer: self.contentNode.layer, from: 0.9, to: 1.0, completion: nil)
|
||||
self.contentNode.alpha = 0.0
|
||||
animation.animator.updateAlpha(layer: self.contentNode.layer, alpha: 1.0, completion: nil)
|
||||
animation.animator.updatePosition(layer: self.contentNode.layer, position: updatedPosition, completion: nil)
|
||||
}
|
||||
} else {
|
||||
self.contentNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
apply(animation, synchronousLoads, applyInfo)
|
||||
|
||||
strongSelf.contentNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -472,9 +527,9 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
|||
}
|
||||
switch websiteType(of: content.websiteName) {
|
||||
case .twitter:
|
||||
return .url(url: "https://twitter.com/\(mention)", concealed: false)
|
||||
return .url(url: "https://twitter.com/\(mention)", concealed: false, activate: nil)
|
||||
case .instagram:
|
||||
return .url(url: "https://instagram.com/\(mention)", concealed: false)
|
||||
return .url(url: "https://instagram.com/\(mention)", concealed: false, activate: nil)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
@ -487,9 +542,9 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
|||
}
|
||||
switch websiteType(of: content.websiteName) {
|
||||
case .twitter:
|
||||
return .url(url: "https://twitter.com/hashtag/\(hashtag)", concealed: false)
|
||||
return .url(url: "https://twitter.com/hashtag/\(hashtag)", concealed: false, activate: nil)
|
||||
case .instagram:
|
||||
return .url(url: "https://instagram.com/explore/tags/\(hashtag)", concealed: false)
|
||||
return .url(url: "https://instagram.com/explore/tags/\(hashtag)", concealed: false, activate: nil)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -274,9 +274,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||
}, updateMessageReaction: { _, _ in
|
||||
}, activateMessagePinch: { _ in
|
||||
}, openMessageContextActions: { _, _, _, _ in
|
||||
}, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
|
||||
}, navigateToMessage: { _, _, _ in }, navigateToMessageStandalone: { _ in
|
||||
}, navigateToThreadMessage: { _, _, _ in
|
||||
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { [weak self] url, _, _, _ in
|
||||
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { [weak self] url, _, _, _, _ in
|
||||
self?.openUrl(url)
|
||||
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
|
||||
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() {
|
||||
|
|
|
|||
|
|
@ -239,7 +239,11 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
|||
if let quote = strongSelf.quote {
|
||||
//TODO:localize
|
||||
titleText = "Reply to quote by \(authorName)"
|
||||
strongSelf.textNode.attributedText = NSAttributedString(string: quote.text, font: textFont, textColor: strongSelf.theme.chat.inputPanel.primaryTextColor)
|
||||
|
||||
let textColor = strongSelf.theme.chat.inputPanel.primaryTextColor
|
||||
let quoteText = stringWithAppliedEntities(trimToLineCount(quote.text, lineCount: 1), entities: quote.entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message)
|
||||
|
||||
strongSelf.textNode.attributedText = quoteText
|
||||
} else {
|
||||
titleText = strongSelf.strings.Conversation_ReplyMessagePanelTitle(authorName).string
|
||||
strongSelf.textNode.attributedText = messageText
|
||||
|
|
@ -286,7 +290,7 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
|||
let text: String
|
||||
//TODO:localize
|
||||
if let (size, _, _) = strongSelf.validLayout, size.width > 320.0 {
|
||||
text = "Tap here for options"
|
||||
text = "Tap here for reply options"
|
||||
} else {
|
||||
text = "Tap here for forwarding options"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,13 +20,11 @@ import MultiAnimationRenderer
|
|||
|
||||
public struct ChatInterfaceHighlightedState: Equatable {
|
||||
public let messageStableId: UInt32
|
||||
public let quote: String?
|
||||
|
||||
public init(messageStableId: UInt32) {
|
||||
public init(messageStableId: UInt32, quote: String?) {
|
||||
self.messageStableId = messageStableId
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChatInterfaceHighlightedState, rhs: ChatInterfaceHighlightedState) -> Bool {
|
||||
return lhs.messageStableId == rhs.messageStableId
|
||||
self.quote = quote
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,6 +73,16 @@ public protocol ChatMessageTransitionProtocol: ASDisplayNode {
|
|||
|
||||
}
|
||||
|
||||
public struct NavigateToMessageParams {
|
||||
public var timestamp: Double?
|
||||
public var quote: String?
|
||||
|
||||
public init(timestamp: Double?, quote: String?) {
|
||||
self.timestamp = timestamp
|
||||
self.quote = quote
|
||||
}
|
||||
}
|
||||
|
||||
public final class ChatControllerInteraction {
|
||||
public enum OpenPeerSource {
|
||||
case `default`
|
||||
|
|
@ -90,7 +98,7 @@ public final class ChatControllerInteraction {
|
|||
public let openMessageReactionContextMenu: (Message, ContextExtractedContentContainingView, ContextGesture?, MessageReaction.Reaction) -> Void
|
||||
public let activateMessagePinch: (PinchSourceContainerNode) -> Void
|
||||
public let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void
|
||||
public let navigateToMessage: (MessageId, MessageId) -> Void
|
||||
public let navigateToMessage: (MessageId, MessageId, NavigateToMessageParams) -> Void
|
||||
public let navigateToMessageStandalone: (MessageId) -> Void
|
||||
public let navigateToThreadMessage: (PeerId, Int64, MessageId?) -> Void
|
||||
public let tapMessage: ((Message) -> Void)?
|
||||
|
|
@ -105,7 +113,7 @@ public final class ChatControllerInteraction {
|
|||
public let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool, Bool) -> Void
|
||||
public let requestMessageActionUrlAuth: (String, MessageActionUrlSubject) -> Void
|
||||
public let activateSwitchInline: (PeerId?, String, ReplyMarkupButtonAction.PeerTypes?) -> Void
|
||||
public let openUrl: (String, Bool, Bool?, Message?) -> Void
|
||||
public let openUrl: (String, Bool, Bool?, Message?, Promise<Bool>?) -> Void
|
||||
public let shareCurrentLocation: () -> Void
|
||||
public let shareAccountContact: () -> Void
|
||||
public let sendBotCommand: (MessageId?, String) -> Void
|
||||
|
|
@ -204,7 +212,7 @@ public final class ChatControllerInteraction {
|
|||
updateMessageReaction: @escaping (Message, ChatControllerInteractionReaction) -> Void,
|
||||
activateMessagePinch: @escaping (PinchSourceContainerNode) -> Void,
|
||||
openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void,
|
||||
navigateToMessage: @escaping (MessageId, MessageId) -> Void,
|
||||
navigateToMessage: @escaping (MessageId, MessageId, NavigateToMessageParams) -> Void,
|
||||
navigateToMessageStandalone: @escaping (MessageId) -> Void,
|
||||
navigateToThreadMessage: @escaping (PeerId, Int64, MessageId?) -> Void,
|
||||
tapMessage: ((Message) -> Void)?,
|
||||
|
|
@ -219,7 +227,7 @@ public final class ChatControllerInteraction {
|
|||
requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void,
|
||||
requestMessageActionUrlAuth: @escaping (String, MessageActionUrlSubject) -> Void,
|
||||
activateSwitchInline: @escaping (PeerId?, String, ReplyMarkupButtonAction.PeerTypes?) -> Void,
|
||||
openUrl: @escaping (String, Bool, Bool?, Message?) -> Void,
|
||||
openUrl: @escaping (String, Bool, Bool?, Message?, Promise<Bool>?) -> Void,
|
||||
shareCurrentLocation: @escaping () -> Void,
|
||||
shareAccountContact: @escaping () -> Void,
|
||||
sendBotCommand: @escaping (MessageId?, String) -> Void,
|
||||
|
|
|
|||
|
|
@ -1393,7 +1393,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode,
|
|||
chatControllerInteraction.toggleMessagesSelection(messageId, selected)
|
||||
},
|
||||
openUrl: { url, param1, param2, message in
|
||||
chatControllerInteraction.openUrl(url, param1, param2, message)
|
||||
chatControllerInteraction.openUrl(url, param1, param2, message, nil)
|
||||
},
|
||||
openInstantPage: { message, data in
|
||||
chatControllerInteraction.openInstantPage(message, data)
|
||||
|
|
|
|||
|
|
@ -1,82 +0,0 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
|
||||
public final class RichTextView: UIView {
|
||||
public final class Params: Equatable {
|
||||
let string: NSAttributedString
|
||||
let constrainedSize: CGSize
|
||||
|
||||
public init(
|
||||
string: NSAttributedString,
|
||||
constrainedSize: CGSize
|
||||
) {
|
||||
self.string = string
|
||||
self.constrainedSize = constrainedSize
|
||||
}
|
||||
|
||||
public static func ==(lhs: Params, rhs: Params) -> Bool {
|
||||
if !lhs.string.isEqual(to: rhs.string) {
|
||||
return false
|
||||
}
|
||||
if lhs.constrainedSize != rhs.constrainedSize {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public final class LayoutData: Equatable {
|
||||
init() {
|
||||
}
|
||||
|
||||
public static func ==(lhs: LayoutData, rhs: LayoutData) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public final class AsyncResult {
|
||||
public let view: () -> RichTextView
|
||||
public let layoutData: LayoutData
|
||||
|
||||
init(view: @escaping () -> RichTextView, layoutData: LayoutData) {
|
||||
self.view = view
|
||||
self.layoutData = layoutData
|
||||
}
|
||||
}
|
||||
|
||||
private static func performLayout(params: Params) -> LayoutData {
|
||||
return LayoutData()
|
||||
}
|
||||
|
||||
public static func updateAsync(_ view: RichTextView?) -> (Params) -> AsyncResult {
|
||||
return { params in
|
||||
let layoutData = performLayout(params: params)
|
||||
|
||||
return AsyncResult(
|
||||
view: {
|
||||
let view = view ?? RichTextView(frame: CGRect())
|
||||
view.layoutData = layoutData
|
||||
return view
|
||||
},
|
||||
layoutData: layoutData
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var layoutData: LayoutData?
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func draw(_ rect: CGRect) {
|
||||
guard let layoutData = self.layoutData else {
|
||||
return
|
||||
}
|
||||
let _ = layoutData
|
||||
}
|
||||
}
|
||||
|
|
@ -74,25 +74,6 @@ final class StoryItemLoadingEffectView: UIView {
|
|||
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
|
||||
/*
|
||||
let numColors = 7
|
||||
var locations: [CGFloat] = []
|
||||
var colors: [CGColor] = []
|
||||
for i in 0 ..< numColors {
|
||||
let position: CGFloat = CGFloat(i) / CGFloat(numColors - 1)
|
||||
locations.append(position)
|
||||
|
||||
let distanceFromCenterFraction: CGFloat = max(0.0, min(1.0, abs(position - 0.5) / 0.5))
|
||||
let colorAlpha = sin((1.0 - distanceFromCenterFraction) * CGFloat.pi * 0.5)
|
||||
|
||||
colors.append(foregroundColor.withMultipliedAlpha(colorAlpha).cgColor)
|
||||
}
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())*/
|
||||
})
|
||||
}
|
||||
self.backgroundView.image = generateGradient(effectAlpha)
|
||||
|
|
@ -109,11 +90,6 @@ final class StoryItemLoadingEffectView: UIView {
|
|||
}
|
||||
|
||||
private func updateAnimations(size: CGSize) {
|
||||
/*if "".isEmpty {
|
||||
self.backgroundView.center = CGPoint(x: size.width * 0.5, y: size.height * 0.5)
|
||||
return
|
||||
}*/
|
||||
|
||||
if self.backgroundView.layer.animation(forKey: "shimmer") != nil || (self.playOnce && self.didPlayOnce) {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "RichTextView",
|
||||
module_name = "RichTextView",
|
||||
name = "TextLoadingEffect",
|
||||
module_name = "TextLoadingEffect",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
|
|
@ -10,6 +10,9 @@ swift_library(
|
|||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/Components/HierarchyTrackingLayer",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AppBundle
|
||||
import HierarchyTrackingLayer
|
||||
|
||||
private let shadowImage: UIImage? = {
|
||||
UIImage(named: "Stories/PanelGradient")
|
||||
}()
|
||||
|
||||
public final class TextLoadingEffectView: UIView {
|
||||
let hierarchyTrackingLayer: HierarchyTrackingLayer
|
||||
|
||||
private let maskContentsView: UIView
|
||||
private let maskHighlightNode: LinkHighlightingNode
|
||||
|
||||
private let maskBorderContentsView: UIView
|
||||
private let maskBorderHighlightNode: LinkHighlightingNode
|
||||
|
||||
private let backgroundView: UIImageView
|
||||
private let borderBackgroundView: UIImageView
|
||||
|
||||
private let duration: Double
|
||||
private let gradientWidth: CGFloat
|
||||
|
||||
private var size: CGSize?
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
self.hierarchyTrackingLayer = HierarchyTrackingLayer()
|
||||
|
||||
self.maskContentsView = UIView()
|
||||
self.maskHighlightNode = LinkHighlightingNode(color: .black)
|
||||
self.maskHighlightNode.useModernPathCalculation = true
|
||||
|
||||
self.maskBorderContentsView = UIView()
|
||||
self.maskBorderHighlightNode = LinkHighlightingNode(color: .black)
|
||||
self.maskBorderHighlightNode.borderOnly = true
|
||||
self.maskBorderHighlightNode.useModernPathCalculation = true
|
||||
self.maskBorderContentsView.addSubview(self.maskBorderHighlightNode.view)
|
||||
|
||||
self.backgroundView = UIImageView()
|
||||
self.borderBackgroundView = UIImageView()
|
||||
|
||||
self.gradientWidth = 120.0
|
||||
self.duration = 1.0
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.isUserInteractionEnabled = false
|
||||
|
||||
self.maskContentsView.mask = self.maskHighlightNode.view
|
||||
self.maskContentsView.addSubview(self.backgroundView)
|
||||
self.addSubview(self.maskContentsView)
|
||||
|
||||
self.maskBorderContentsView.mask = self.maskBorderHighlightNode.view
|
||||
self.maskBorderContentsView.addSubview(self.borderBackgroundView)
|
||||
self.addSubview(self.maskBorderContentsView)
|
||||
|
||||
let generateGradient: (CGFloat) -> UIImage? = { baseAlpha in
|
||||
return generateImage(CGSize(width: self.gradientWidth, height: 16.0), opaque: false, scale: 1.0, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let foregroundColor = UIColor(white: 1.0, alpha: min(1.0, baseAlpha * 4.0))
|
||||
|
||||
if let shadowImage {
|
||||
UIGraphicsPushContext(context)
|
||||
|
||||
for i in 0 ..< 2 {
|
||||
let shadowFrame = CGRect(origin: CGPoint(x: CGFloat(i) * (size.width * 0.5), y: 0.0), size: CGSize(width: size.width * 0.5, height: size.height))
|
||||
|
||||
context.saveGState()
|
||||
context.translateBy(x: shadowFrame.midX, y: shadowFrame.midY)
|
||||
context.rotate(by: CGFloat(i == 0 ? 1.0 : -1.0) * CGFloat.pi * 0.5)
|
||||
let adjustedRect = CGRect(origin: CGPoint(x: -shadowFrame.height * 0.5, y: -shadowFrame.width * 0.5), size: CGSize(width: shadowFrame.height, height: shadowFrame.width))
|
||||
|
||||
context.clip(to: adjustedRect, mask: shadowImage.cgImage!)
|
||||
context.setFillColor(foregroundColor.cgColor)
|
||||
context.fill(adjustedRect)
|
||||
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
})?.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
|
||||
self.backgroundView.image = generateGradient(0.5)
|
||||
self.borderBackgroundView.image = generateGradient(1.0)
|
||||
|
||||
self.layer.addSublayer(self.hierarchyTrackingLayer)
|
||||
self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in
|
||||
guard let self, let size = self.size else {
|
||||
return
|
||||
}
|
||||
self.updateAnimations(size: size)
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func updateAnimations(size: CGSize) {
|
||||
if self.backgroundView.layer.animation(forKey: "shimmer") != nil {
|
||||
return
|
||||
}
|
||||
|
||||
let animation = self.backgroundView.layer.makeAnimation(from: 0.0 as NSNumber, to: (size.width + self.gradientWidth + size.width * 0.0) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: self.duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
||||
animation.repeatCount = Float.infinity
|
||||
self.backgroundView.layer.add(animation, forKey: "shimmer")
|
||||
self.borderBackgroundView.layer.add(animation, forKey: "shimmer")
|
||||
}
|
||||
|
||||
public func update(color: UIColor, textNode: TextNode, range: NSRange) {
|
||||
var rectsSet: [CGRect] = []
|
||||
if let cachedLayout = textNode.cachedLayout {
|
||||
if let rects = cachedLayout.rangeRects(in: range)?.rects, !rects.isEmpty {
|
||||
rectsSet = rects
|
||||
}
|
||||
}
|
||||
|
||||
let maskFrame = CGRect(origin: CGPoint(), size: textNode.bounds.size).insetBy(dx: -4.0, dy: -4.0)
|
||||
|
||||
self.maskContentsView.backgroundColor = color.withAlphaComponent(0.1)
|
||||
self.maskBorderContentsView.backgroundColor = color.withAlphaComponent(0.12)
|
||||
|
||||
self.backgroundView.tintColor = color
|
||||
self.borderBackgroundView.tintColor = color
|
||||
|
||||
self.maskContentsView.frame = maskFrame
|
||||
self.maskBorderContentsView.frame = maskFrame
|
||||
|
||||
self.maskHighlightNode.updateRects(rectsSet)
|
||||
self.maskHighlightNode.frame = CGRect(origin: CGPoint(x: -maskFrame.minX, y: -maskFrame.minY), size: CGSize())
|
||||
|
||||
self.maskBorderHighlightNode.updateRects(rectsSet)
|
||||
self.maskBorderHighlightNode.frame = CGRect(origin: CGPoint(x: -maskFrame.minX, y: -maskFrame.minY), size: CGSize())
|
||||
|
||||
if self.size != maskFrame.size {
|
||||
self.size = maskFrame.size
|
||||
|
||||
self.backgroundView.frame = CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: maskFrame.height))
|
||||
self.borderBackgroundView.frame = CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: maskFrame.height))
|
||||
|
||||
self.updateAnimations(size: maskFrame.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -339,7 +339,7 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch
|
|||
return
|
||||
}
|
||||
|
||||
selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(ChatInterfaceState.ReplyMessageSubject(messageId: replySubject.messageId, quote: EngineMessageReplyQuote(text: textSelection.text, entities: textSelection.entities))).withoutSelectionState() }) })
|
||||
selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(ChatInterfaceState.ReplyMessageSubject(messageId: replySubject.messageId, quote: EngineMessageReplyQuote(text: textSelection.text, entities: textSelection.entities, media: nil))).withoutSelectionState() }) })
|
||||
|
||||
f(.default)
|
||||
})))
|
||||
|
|
@ -381,7 +381,7 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch
|
|||
return
|
||||
}
|
||||
|
||||
selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(ChatInterfaceState.ReplyMessageSubject(messageId: replySubject.messageId, quote: EngineMessageReplyQuote(text: textSelection.text, entities: textSelection.entities))).withoutSelectionState() }) })
|
||||
selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(ChatInterfaceState.ReplyMessageSubject(messageId: replySubject.messageId, quote: EngineMessageReplyQuote(text: textSelection.text, entities: textSelection.entities, media: nil))).withoutSelectionState() }) })
|
||||
|
||||
f(.default)
|
||||
})))
|
||||
|
|
@ -603,7 +603,15 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
|||
guard let peerId = selfController.chatLocation.peerId else {
|
||||
return nil
|
||||
}
|
||||
guard let initialUrlPreview = selfController.presentationInterfaceState.urlPreview else {
|
||||
|
||||
let initialUrlPreview: ChatPresentationInterfaceState.UrlPreview?
|
||||
if selfController.presentationInterfaceState.interfaceState.editMessage != nil {
|
||||
initialUrlPreview = selfController.presentationInterfaceState.editingUrlPreview
|
||||
} else {
|
||||
initialUrlPreview = selfController.presentationInterfaceState.urlPreview
|
||||
}
|
||||
|
||||
guard let initialUrlPreview else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -612,23 +620,53 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
|||
replySelectionState.get()
|
||||
)
|
||||
|> map { state, replySelectionState -> ChatControllerSubject.LinkOptions in
|
||||
let urlPreview = state.urlPreview ?? initialUrlPreview
|
||||
let urlPreview: ChatPresentationInterfaceState.UrlPreview
|
||||
if state.interfaceState.editMessage != nil {
|
||||
urlPreview = state.editingUrlPreview ?? initialUrlPreview
|
||||
} else {
|
||||
urlPreview = state.urlPreview ?? initialUrlPreview
|
||||
}
|
||||
|
||||
var webpageOptions: TelegramMediaWebpageDisplayOptions = .default
|
||||
var webpageHasLargeMedia = false
|
||||
if case let .Loaded(content) = urlPreview.webPage.content {
|
||||
if let isMediaLargeByDefault = content.isMediaLargeByDefault {
|
||||
if isMediaLargeByDefault {
|
||||
webpageHasLargeMedia = true
|
||||
}
|
||||
} else {
|
||||
webpageHasLargeMedia = true
|
||||
}
|
||||
}
|
||||
|
||||
if let (_, webpage) = state.urlPreview, case let .Loaded(content) = webpage.content {
|
||||
webpageOptions = content.displayOptions
|
||||
let composeInputText: NSAttributedString = state.interfaceState.effectiveInputState.inputText
|
||||
|
||||
var replyMessageId: EngineMessage.Id?
|
||||
var replyQuote: String?
|
||||
|
||||
if state.interfaceState.editMessage == nil {
|
||||
replyMessageId = state.interfaceState.replyMessageSubject?.messageId
|
||||
replyQuote = replySelectionState.quote?.text
|
||||
}
|
||||
|
||||
let inputText = chatInputStateStringWithAppliedEntities(composeInputText.string, entities: generateChatInputTextEntities(composeInputText, generateLinks: false))
|
||||
|
||||
var largeMedia = false
|
||||
if webpageHasLargeMedia {
|
||||
largeMedia = urlPreview.largeMedia ?? true
|
||||
} else {
|
||||
largeMedia = false
|
||||
}
|
||||
|
||||
return ChatControllerSubject.LinkOptions(
|
||||
messageText: state.interfaceState.composeInputState.inputText.string,
|
||||
messageEntities: generateChatInputTextEntities(state.interfaceState.composeInputState.inputText, generateLinks: true),
|
||||
replyMessageId: state.interfaceState.replyMessageSubject?.messageId,
|
||||
replyQuote: replySelectionState.quote?.text,
|
||||
url: urlPreview.0,
|
||||
webpage: urlPreview.1,
|
||||
linkBelowText: webpageOptions.position != .aboveText,
|
||||
largeMedia: webpageOptions.largeMedia != false
|
||||
messageText: composeInputText.string,
|
||||
messageEntities: generateChatInputTextEntities(composeInputText, generateLinks: true),
|
||||
hasAlternativeLinks: detectUrls(inputText).count > 1,
|
||||
replyMessageId: replyMessageId,
|
||||
replyQuote: replyQuote,
|
||||
url: urlPreview.url,
|
||||
webpage: urlPreview.webPage,
|
||||
linkBelowText: urlPreview.positionBelowText,
|
||||
largeMedia: largeMedia
|
||||
)
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
|
@ -640,9 +678,9 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
|||
|
||||
let items = linkOptions
|
||||
|> deliverOnMainQueue
|
||||
|> map { [weak selfController] linkOptions -> [ContextMenuItem] in
|
||||
|> map { [weak selfController] linkOptions -> ContextController.Items in
|
||||
guard let selfController else {
|
||||
return []
|
||||
return ContextController.Items(id: AnyHashable(linkOptions.url), content: .list([]))
|
||||
}
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
|
|
@ -657,15 +695,19 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
|||
}
|
||||
}, action: { [weak selfController] _, f in
|
||||
selfController?.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
|
||||
guard var urlPreview = state.urlPreview else {
|
||||
return state
|
||||
if state.interfaceState.editMessage != nil {
|
||||
guard var urlPreview = state.editingUrlPreview else {
|
||||
return state
|
||||
}
|
||||
urlPreview.positionBelowText = false
|
||||
return state.updatedEditingUrlPreview(urlPreview)
|
||||
} else {
|
||||
guard var urlPreview = state.urlPreview else {
|
||||
return state
|
||||
}
|
||||
urlPreview.positionBelowText = false
|
||||
return state.updatedUrlPreview(urlPreview)
|
||||
}
|
||||
if case let .Loaded(content) = urlPreview.1.content {
|
||||
var displayOptions = content.displayOptions
|
||||
displayOptions.position = .aboveText
|
||||
urlPreview = (urlPreview.0, TelegramMediaWebpage(webpageId: urlPreview.1.webpageId, content: .Loaded(content.withDisplayOptions(displayOptions))))
|
||||
}
|
||||
return state.updatedUrlPreview(urlPreview)
|
||||
})
|
||||
})))
|
||||
|
||||
|
|
@ -677,20 +719,24 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
|||
}
|
||||
}, action: { [weak selfController] _, f in
|
||||
selfController?.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
|
||||
guard var urlPreview = state.urlPreview else {
|
||||
return state
|
||||
if state.interfaceState.editMessage != nil {
|
||||
guard var urlPreview = state.editingUrlPreview else {
|
||||
return state
|
||||
}
|
||||
urlPreview.positionBelowText = true
|
||||
return state.updatedEditingUrlPreview(urlPreview)
|
||||
} else {
|
||||
guard var urlPreview = state.urlPreview else {
|
||||
return state
|
||||
}
|
||||
urlPreview.positionBelowText = true
|
||||
return state.updatedUrlPreview(urlPreview)
|
||||
}
|
||||
if case let .Loaded(content) = urlPreview.1.content {
|
||||
var displayOptions = content.displayOptions
|
||||
displayOptions.position = .belowText
|
||||
urlPreview = (urlPreview.0, TelegramMediaWebpage(webpageId: urlPreview.1.webpageId, content: .Loaded(content.withDisplayOptions(displayOptions))))
|
||||
}
|
||||
return state.updatedUrlPreview(urlPreview)
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
if "".isEmpty {
|
||||
if case let .Loaded(content) = linkOptions.webpage.content, let isMediaLargeByDefault = content.isMediaLargeByDefault, isMediaLargeByDefault {
|
||||
if !items.isEmpty {
|
||||
items.append(.separator)
|
||||
}
|
||||
|
|
@ -705,15 +751,19 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
|||
}
|
||||
}, action: { [weak selfController] _, f in
|
||||
selfController?.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
|
||||
guard var urlPreview = state.urlPreview else {
|
||||
return state
|
||||
if state.interfaceState.editMessage != nil {
|
||||
guard var urlPreview = state.editingUrlPreview else {
|
||||
return state
|
||||
}
|
||||
urlPreview.largeMedia = false
|
||||
return state.updatedEditingUrlPreview(urlPreview)
|
||||
} else {
|
||||
guard var urlPreview = state.urlPreview else {
|
||||
return state
|
||||
}
|
||||
urlPreview.largeMedia = false
|
||||
return state.updatedUrlPreview(urlPreview)
|
||||
}
|
||||
if case let .Loaded(content) = urlPreview.1.content {
|
||||
var displayOptions = content.displayOptions
|
||||
displayOptions.largeMedia = false
|
||||
urlPreview = (urlPreview.0, TelegramMediaWebpage(webpageId: urlPreview.1.webpageId, content: .Loaded(content.withDisplayOptions(displayOptions))))
|
||||
}
|
||||
return state.updatedUrlPreview(urlPreview)
|
||||
})
|
||||
})))
|
||||
|
||||
|
|
@ -725,15 +775,19 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
|||
}
|
||||
}, action: { [weak selfController] _, f in
|
||||
selfController?.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
|
||||
guard var urlPreview = state.urlPreview else {
|
||||
return state
|
||||
if state.interfaceState.editMessage != nil {
|
||||
guard var urlPreview = state.editingUrlPreview else {
|
||||
return state
|
||||
}
|
||||
urlPreview.largeMedia = true
|
||||
return state.updatedEditingUrlPreview(urlPreview)
|
||||
} else {
|
||||
guard var urlPreview = state.urlPreview else {
|
||||
return state
|
||||
}
|
||||
urlPreview.largeMedia = true
|
||||
return state.updatedUrlPreview(urlPreview)
|
||||
}
|
||||
if case let .Loaded(content) = urlPreview.1.content {
|
||||
var displayOptions = content.displayOptions
|
||||
displayOptions.largeMedia = true
|
||||
urlPreview = (urlPreview.0, TelegramMediaWebpage(webpageId: urlPreview.1.webpageId, content: .Loaded(content.withDisplayOptions(displayOptions))))
|
||||
}
|
||||
return state.updatedUrlPreview(urlPreview)
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
|
@ -755,32 +809,44 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
|||
f(.default)
|
||||
})))
|
||||
|
||||
return items
|
||||
return ContextController.Items(id: AnyHashable(linkOptions.url), content: .list(items))
|
||||
}
|
||||
|
||||
chatController.performOpenURL = { [weak selfController] message, url in
|
||||
chatController.performOpenURL = { [weak selfController] message, url, progress in
|
||||
guard let selfController else {
|
||||
return
|
||||
}
|
||||
|
||||
//TODO:
|
||||
//func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: AccountContext, currentQuery: String?) -> (String?, Signal<(TelegramMediaWebpage?) -> TelegramMediaWebpage?, NoError>)? {
|
||||
if let (updatedUrlPreviewUrl, signal) = urlPreviewStateForInputText(NSAttributedString(string: url), context: selfController.context, currentQuery: nil), let updatedUrlPreviewUrl {
|
||||
progress?.set(.single(true))
|
||||
let _ = (signal
|
||||
|> afterDisposed {
|
||||
progress?.set(.single(false))
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak selfController] result in
|
||||
guard let selfController else {
|
||||
return
|
||||
}
|
||||
|
||||
selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { state in
|
||||
if let webpage = result(nil), var urlPreview = state.urlPreview {
|
||||
if case let .Loaded(content) = urlPreview.1.content, case let .Loaded(newContent) = webpage.content {
|
||||
urlPreview = (updatedUrlPreviewUrl, TelegramMediaWebpage(webpageId: webpage.webpageId, content: .Loaded(newContent.withDisplayOptions(content.displayOptions))))
|
||||
if state.interfaceState.editMessage != nil {
|
||||
if let webpage = result(nil), var urlPreview = state.editingUrlPreview {
|
||||
urlPreview.url = updatedUrlPreviewUrl
|
||||
urlPreview.webPage = webpage
|
||||
|
||||
return state.updatedEditingUrlPreview(urlPreview)
|
||||
} else {
|
||||
return state
|
||||
}
|
||||
|
||||
return state.updatedUrlPreview(urlPreview)
|
||||
} else {
|
||||
return state
|
||||
if let webpage = result(nil), var urlPreview = state.urlPreview {
|
||||
urlPreview.url = updatedUrlPreviewUrl
|
||||
urlPreview.webPage = webpage
|
||||
|
||||
return state.updatedUrlPreview(urlPreview)
|
||||
} else {
|
||||
return state
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -792,7 +858,7 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
|||
id: AnyHashable(OptionsId.link),
|
||||
title: "Link",
|
||||
source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)),
|
||||
items: items |> map { ContextController.Items(content: .list($0)) }
|
||||
items: items
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ enum ChatRecordingActivity {
|
|||
}
|
||||
|
||||
public enum NavigateToMessageLocation {
|
||||
case id(MessageId, Double?)
|
||||
case id(MessageId, NavigateToMessageParams)
|
||||
case index(MessageIndex)
|
||||
case upperBound(PeerId)
|
||||
|
||||
|
|
@ -509,7 +509,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
return .widthMultiplier(factor: 0.35, min: 16.0, max: 200.0)
|
||||
}
|
||||
|
||||
var scheduledScrollToMessageId: (MessageId, Double?)?
|
||||
var scheduledScrollToMessageId: (MessageId, NavigateToMessageParams)?
|
||||
|
||||
public var purposefulAction: (() -> Void)?
|
||||
var updatedClosedPinnedMessageId: ((MessageId) -> Void)?
|
||||
|
|
@ -553,7 +553,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
var storyStats: PeerStoryStats?
|
||||
|
||||
var performTextSelectionAction: ((Message?, Bool, NSAttributedString, TextSelectionAction) -> Void)?
|
||||
var performOpenURL: ((Message?, String) -> Void)?
|
||||
var performOpenURL: ((Message?, String, Promise<Bool>?) -> Void)?
|
||||
|
||||
public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, botAppStart: ChatControllerInitialBotAppStart? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [ChatNavigationStackItem] = []) {
|
||||
let _ = ChatControllerCount.modify { value in
|
||||
|
|
@ -926,7 +926,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
case .pinnedMessageUpdated:
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, nil))
|
||||
strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text)))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -935,7 +935,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
case .gameScore:
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, nil))
|
||||
strongSelf.navigateToMessage(from: message.id, to: .id(attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text)))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -1090,7 +1090,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
case .setSameChatWallpaper:
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute {
|
||||
strongSelf.controllerInteraction?.navigateToMessage(message.id, attribute.messageId)
|
||||
strongSelf.controllerInteraction?.navigateToMessage(message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: nil))
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -2170,10 +2170,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
strongSelf.window?.presentInGlobalOverlay(pinchController)
|
||||
}, openMessageContextActions: { message, node, rect, gesture in
|
||||
gesture?.cancel()
|
||||
}, navigateToMessage: { [weak self] fromId, id in
|
||||
self?.navigateToMessage(from: fromId, to: .id(id, nil), forceInCurrentChat: fromId.peerId == id.peerId)
|
||||
}, navigateToMessage: { [weak self] fromId, id, params in
|
||||
self?.navigateToMessage(from: fromId, to: .id(id, params), forceInCurrentChat: fromId.peerId == id.peerId)
|
||||
}, navigateToMessageStandalone: { [weak self] id in
|
||||
self?.navigateToMessage(from: nil, to: .id(id, nil), forceInCurrentChat: false)
|
||||
self?.navigateToMessage(from: nil, to: .id(id, NavigateToMessageParams(timestamp: nil, quote: nil)), forceInCurrentChat: false)
|
||||
}, navigateToThreadMessage: { [weak self] peerId, threadId, messageId in
|
||||
if let context = self?.context, let navigationController = self?.effectiveNavigationController {
|
||||
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, keepStack: .always).startStandalone()
|
||||
|
|
@ -2743,7 +2743,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
strongSelf.openPeer(peer: nil, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: nil, peekData: nil), fromMessage: nil, peerTypes: peerTypes)
|
||||
}
|
||||
}
|
||||
}, openUrl: { [weak self] url, concealed, _, message in
|
||||
}, openUrl: { [weak self] url, concealed, _, message, progress in
|
||||
if let strongSelf = self {
|
||||
var skipConcealedAlert = false
|
||||
if let author = message?.author, author.isVerified {
|
||||
|
|
@ -2755,8 +2755,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}
|
||||
|
||||
if let performOpenURL = strongSelf.performOpenURL {
|
||||
performOpenURL(message, url)
|
||||
performOpenURL(message, url, progress)
|
||||
} else {
|
||||
progress?.set(.single(false))
|
||||
strongSelf.openUrl(url, concealed: concealed, skipConcealedAlert: skipConcealedAlert, message: message)
|
||||
}
|
||||
}
|
||||
|
|
@ -2834,7 +2835,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
strongSelf.chatDisplayNode.collapseInput()
|
||||
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreview(nil) }
|
||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreviews([]) }
|
||||
})
|
||||
}
|
||||
}, nil)
|
||||
|
|
@ -3364,7 +3365,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
items.append(ActionSheetButtonItem(title: url.title, color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllerInteraction?.openUrl(url.url, false, false, message)
|
||||
strongSelf.controllerInteraction?.openUrl(url.url, false, false, message, nil)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
@ -3797,7 +3798,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {
|
||||
let _ = strongSelf.controllerInteraction?.openMessage(message, .timecode(Double(timestamp)))
|
||||
} else {
|
||||
strongSelf.navigateToMessage(messageLocation: .id(messageId, Double(timestamp)), animated: true, forceInCurrentChat: true)
|
||||
strongSelf.navigateToMessage(messageLocation: .id(messageId, NavigateToMessageParams(timestamp: Double(timestamp), quote: nil)), animated: true, forceInCurrentChat: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3853,7 +3854,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}
|
||||
|
||||
let inlineStickers: [MediaId: TelegramMediaFile] = [:]
|
||||
strongSelf.editMessageDisposable.set((strongSelf.context.engine.messages.requestEditMessage(messageId: messageId, text: message.text, media: .keep, entities: entities, inlineStickers: inlineStickers, disableUrlPreview: false, scheduleTime: time) |> deliverOnMainQueue).startStrict(next: { result in
|
||||
strongSelf.editMessageDisposable.set((strongSelf.context.engine.messages.requestEditMessage(messageId: messageId, text: message.text, media: .keep, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: nil, disableUrlPreview: false, scheduleTime: time) |> deliverOnMainQueue).startStrict(next: { result in
|
||||
}, error: { error in
|
||||
}))
|
||||
}
|
||||
|
|
@ -3967,8 +3968,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
if let messageId = message?.id, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {
|
||||
var quoteData: EngineMessageReplyQuote?
|
||||
|
||||
let quoteText = (message.text as NSString).substring(with: NSRange(location: range.lowerBound, length: range.upperBound - range.lowerBound))
|
||||
quoteData = EngineMessageReplyQuote(text: quoteText, entities: [])
|
||||
let nsRange = NSRange(location: range.lowerBound, length: range.upperBound - range.lowerBound)
|
||||
let quoteText = (message.text as NSString).substring(with: nsRange)
|
||||
quoteData = EngineMessageReplyQuote(text: quoteText, entities: messageTextEntitiesInRange(entities: message.textEntitiesAttribute?.entities ?? [], range: nsRange, onlyQuoteable: true), media: nil)
|
||||
|
||||
let replySubject = ChatInterfaceState.ReplyMessageSubject(
|
||||
messageId: message.id,
|
||||
|
|
@ -4181,7 +4183,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
strongSelf.openMessageReplies(messageId: threadMessageId, displayProgressInMessage: message.id, isChannelPost: true, atMessage: attribute.messageId, displayModalProgress: false)
|
||||
}
|
||||
} else {
|
||||
strongSelf.navigateToMessage(from: nil, to: .id(attribute.messageId, nil))
|
||||
strongSelf.navigateToMessage(from: nil, to: .id(attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: nil)))
|
||||
}
|
||||
break
|
||||
}
|
||||
|
|
@ -4584,7 +4586,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
case let .peer(id, messageId, startParam):
|
||||
if case let .peer(currentPeerId) = self.chatLocation, currentPeerId == id {
|
||||
if let messageId {
|
||||
self.navigateToMessage(from: nil, to: .id(messageId, nil), rememberInStack: false)
|
||||
self.navigateToMessage(from: nil, to: .id(messageId, NavigateToMessageParams(timestamp: nil, quote: nil)), rememberInStack: false)
|
||||
}
|
||||
} else {
|
||||
let navigationData: ChatControllerInteractionNavigateToPeer
|
||||
|
|
@ -4607,7 +4609,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
case let .join(_, joinHash):
|
||||
self.controllerInteraction?.openJoinLink(joinHash)
|
||||
case let .webPage(_, url):
|
||||
self.controllerInteraction?.openUrl(url, false, false, nil)
|
||||
self.controllerInteraction?.openUrl(url, false, false, nil, nil)
|
||||
}
|
||||
}, openRequestedPeerSelection: { [weak self] messageId, peerType, buttonId in
|
||||
guard let self else {
|
||||
|
|
@ -5311,9 +5313,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
case .reply:
|
||||
//TODO:localize
|
||||
subtitleTextSignal = .single("You can select a specific part to quote")
|
||||
case .link:
|
||||
//TODO:localize
|
||||
subtitleTextSignal = .single("Tap on a link to generate its preview")
|
||||
case let .link(link):
|
||||
subtitleTextSignal = link.options
|
||||
|> map { options -> String? in
|
||||
if options.hasAlternativeLinks {
|
||||
//TODO:localize
|
||||
return "Tap on a link to generate its preview"
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6935,7 +6945,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
func pinnedHistorySignal(anchorMessageId: MessageId?, count: Int) -> Signal<ChatHistoryViewUpdate, NoError> {
|
||||
let location: ChatHistoryLocation
|
||||
if let anchorMessageId = anchorMessageId {
|
||||
location = .InitialSearch(location: .id(anchorMessageId), count: count, highlight: false)
|
||||
location = .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(anchorMessageId), quote: nil), count: count, highlight: false)
|
||||
} else {
|
||||
location = .Initial(count: count)
|
||||
}
|
||||
|
|
@ -8012,15 +8022,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
strongSelf.chatDisplayNode.updatePlainInputSeparatorAlpha(plainInputSeparatorAlpha, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
|
||||
self.chatDisplayNode.historyNode.scrolledToIndex = { [weak self] toIndex, initial in
|
||||
if let strongSelf = self, case let .message(index) = toIndex {
|
||||
self.chatDisplayNode.historyNode.scrolledToIndex = { [weak self] toSubject, initial in
|
||||
if let strongSelf = self, case let .message(index) = toSubject.index {
|
||||
if case let .message(messageSubject, _, _) = strongSelf.subject, initial, case let .id(messageId) = messageSubject, messageId != index.id {
|
||||
if messageId.peerId == index.id.peerId {
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.Conversation_MessageDoesntExist, timeout: nil), elevatedLayout: false, action: { _ in return true }), in: .current)
|
||||
}
|
||||
} else if let controllerInteraction = strongSelf.controllerInteraction {
|
||||
if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(index.id) {
|
||||
let highlightedState = ChatInterfaceHighlightedState(messageStableId: message.stableId)
|
||||
let highlightedState = ChatInterfaceHighlightedState(messageStableId: message.stableId, quote: toSubject.quote)
|
||||
controllerInteraction.highlightedState = highlightedState
|
||||
strongSelf.updateItemNodesHighlightedStates(animated: initial)
|
||||
strongSelf.scrolledToMessageIdValue = ScrolledToMessageId(id: index.id, allowedReplacementDirection: [])
|
||||
|
|
@ -8034,9 +8044,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}
|
||||
}))
|
||||
|
||||
if let (messageId, maybeTimecode) = strongSelf.scheduledScrollToMessageId {
|
||||
if let quote = toSubject.quote, !message.text.contains(quote) {
|
||||
//TODO:localize
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: "Quote not found", timeout: nil), elevatedLayout: false, action: { _ in return true }), in: .current)
|
||||
}
|
||||
|
||||
if let (messageId, params) = strongSelf.scheduledScrollToMessageId {
|
||||
strongSelf.scheduledScrollToMessageId = nil
|
||||
if let timecode = maybeTimecode, message.id == messageId {
|
||||
if let timecode = params.timestamp, message.id == messageId {
|
||||
Queue.mainQueue().after(0.2) {
|
||||
let _ = strongSelf.controllerInteraction?.openMessage(message, .timecode(timecode))
|
||||
}
|
||||
|
|
@ -8334,18 +8349,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
self.chatDisplayNode.dismissUrlPreview = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if let _ = strongSelf.presentationInterfaceState.interfaceState.editMessage {
|
||||
if let (link, _) = strongSelf.presentationInterfaceState.editingUrlPreview {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
$0.updatedInterfaceState {
|
||||
$0.withUpdatedEditMessage($0.editMessage.flatMap { $0.withUpdatedDisableUrlPreview(link) })
|
||||
if let link = strongSelf.presentationInterfaceState.editingUrlPreview?.url {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { presentationInterfaceState in
|
||||
return presentationInterfaceState.updatedInterfaceState { interfaceState in
|
||||
return interfaceState.withUpdatedEditMessage(interfaceState.editMessage.flatMap { editMessage in
|
||||
var editMessage = editMessage
|
||||
if !editMessage.disableUrlPreviews.contains(link) {
|
||||
editMessage.disableUrlPreviews.append(link)
|
||||
}
|
||||
return editMessage
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if let (link, _) = strongSelf.presentationInterfaceState.urlPreview {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
$0.updatedInterfaceState {
|
||||
$0.withUpdatedComposeDisableUrlPreview(link)
|
||||
if let link = strongSelf.presentationInterfaceState.urlPreview?.url {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { presentationInterfaceState in
|
||||
return presentationInterfaceState.updatedInterfaceState { interfaceState in
|
||||
var composeDisableUrlPreviews = interfaceState.composeDisableUrlPreviews
|
||||
if !composeDisableUrlPreviews.contains(link) {
|
||||
composeDisableUrlPreviews.append(link)
|
||||
}
|
||||
return interfaceState.withUpdatedComposeDisableUrlPreviews(composeDisableUrlPreviews)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -8356,7 +8381,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
self.chatDisplayNode.navigateButtons.downPressed = { [weak self] in
|
||||
if let strongSelf = self, strongSelf.isNodeLoaded {
|
||||
if let messageId = strongSelf.historyNavigationStack.removeLast() {
|
||||
strongSelf.navigateToMessage(from: nil, to: .id(messageId.id, nil), rememberInStack: false)
|
||||
strongSelf.navigateToMessage(from: nil, to: .id(messageId.id, NavigateToMessageParams(timestamp: nil, quote: nil)), rememberInStack: false)
|
||||
} else {
|
||||
if case .known = strongSelf.chatDisplayNode.historyNode.visibleContentOffset() {
|
||||
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||
|
|
@ -8379,7 +8404,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
switch result {
|
||||
case let .result(messageId):
|
||||
if let messageId = messageId {
|
||||
strongSelf.navigateToMessage(from: nil, to: .id(messageId, nil))
|
||||
strongSelf.navigateToMessage(from: nil, to: .id(messageId, NavigateToMessageParams(timestamp: nil, quote: nil)))
|
||||
}
|
||||
case .loading:
|
||||
break
|
||||
|
|
@ -8437,7 +8462,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
case let .result(messageId):
|
||||
if let messageId = messageId {
|
||||
strongSelf.chatDisplayNode.historyNode.suspendReadingReactions = true
|
||||
strongSelf.navigateToMessage(from: nil, to: .id(messageId, nil), scrollPosition: .center(.top), completion: {
|
||||
strongSelf.navigateToMessage(from: nil, to: .id(messageId, NavigateToMessageParams(timestamp: nil, quote: nil)), scrollPosition: .center(.top), completion: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
|
@ -8640,29 +8665,31 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
let _ = strongSelf.presentVoiceMessageDiscardAlert(action: {
|
||||
if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
|
||||
var updated = state.updatedInterfaceState {
|
||||
var entities: [MessageTextEntity] = []
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
||||
entities = attribute.entities
|
||||
break
|
||||
}
|
||||
var entities: [MessageTextEntity] = []
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
||||
entities = attribute.entities
|
||||
break
|
||||
}
|
||||
var inputTextMaxLength: Int32 = 4096
|
||||
var webpageUrl: String?
|
||||
for media in message.media {
|
||||
if media is TelegramMediaImage || media is TelegramMediaFile {
|
||||
inputTextMaxLength = strongSelf.context.userLimits.maxCaptionLength
|
||||
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
||||
webpageUrl = content.url
|
||||
}
|
||||
}
|
||||
var inputTextMaxLength: Int32 = 4096
|
||||
var webpageUrl: String?
|
||||
for media in message.media {
|
||||
if media is TelegramMediaImage || media is TelegramMediaFile {
|
||||
inputTextMaxLength = strongSelf.context.userLimits.maxCaptionLength
|
||||
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
||||
webpageUrl = content.url
|
||||
}
|
||||
let inputText = chatInputStateStringWithAppliedEntities(message.text, entities: entities)
|
||||
var disableUrlPreview: String?
|
||||
if let detectedWebpageUrl = detectUrl(inputText), webpageUrl == nil {
|
||||
disableUrlPreview = detectedWebpageUrl
|
||||
}
|
||||
return $0.withUpdatedEditMessage(ChatEditMessageState(messageId: messageId, inputState: ChatTextInputState(inputText: inputText), disableUrlPreview: disableUrlPreview, inputTextMaxLength: inputTextMaxLength))
|
||||
}
|
||||
|
||||
let inputText = chatInputStateStringWithAppliedEntities(message.text, entities: entities)
|
||||
var disableUrlPreviews: [String] = []
|
||||
if webpageUrl == nil {
|
||||
disableUrlPreviews = detectUrls(inputText)
|
||||
}
|
||||
|
||||
var updated = state.updatedInterfaceState { interfaceState in
|
||||
return interfaceState.withUpdatedEditMessage(ChatEditMessageState(messageId: messageId, inputState: ChatTextInputState(inputText: inputText), disableUrlPreviews: disableUrlPreviews, inputTextMaxLength: inputTextMaxLength))
|
||||
}
|
||||
|
||||
updated = updatedChatEditInterfaceMessageState(state: updated, message: message)
|
||||
|
|
@ -8967,9 +8994,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}, editMessage: { [weak self] in
|
||||
if let strongSelf = self, let editMessage = strongSelf.presentationInterfaceState.interfaceState.editMessage {
|
||||
var disableUrlPreview = false
|
||||
if let (link, _) = strongSelf.presentationInterfaceState.editingUrlPreview {
|
||||
if editMessage.disableUrlPreview == link {
|
||||
|
||||
var webpage: TelegramMediaWebpage?
|
||||
var webpagePreviewAttribute: WebpagePreviewMessageAttribute?
|
||||
if let urlPreview = strongSelf.presentationInterfaceState.editingUrlPreview {
|
||||
if editMessage.disableUrlPreviews.contains(urlPreview.url) {
|
||||
disableUrlPreview = true
|
||||
} else {
|
||||
webpage = urlPreview.webPage
|
||||
webpagePreviewAttribute = WebpagePreviewMessageAttribute(leadingPreview: !urlPreview.positionBelowText, forceLargeMedia: urlPreview.largeMedia, isManuallyAdded: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -9022,6 +9055,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
if let editMediaReference = strongSelf.presentationInterfaceState.editMessageState?.mediaReference {
|
||||
media = .update(editMediaReference)
|
||||
updatingMedia = true
|
||||
} else if let webpage {
|
||||
media = .update(.standalone(media: webpage))
|
||||
} else {
|
||||
media = .keep
|
||||
}
|
||||
|
|
@ -9032,8 +9067,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
if let strongSelf = self {
|
||||
if let currentMessage = currentMessage {
|
||||
let currentEntities = currentMessage.textEntitiesAttribute?.entities ?? []
|
||||
if currentMessage.text != text.string || currentEntities != entities || updatingMedia || disableUrlPreview {
|
||||
strongSelf.context.account.pendingUpdateMessageManager.add(messageId: editMessage.messageId, text: text.string, media: media, entities: entitiesAttribute, inlineStickers: inlineStickers, disableUrlPreview: disableUrlPreview)
|
||||
let currentWebpagePreviewAttribute = currentMessage.webpagePreviewAttribute ?? WebpagePreviewMessageAttribute(leadingPreview: false, forceLargeMedia: nil, isManuallyAdded: true)
|
||||
|
||||
if currentMessage.text != text.string || currentEntities != entities || updatingMedia || webpagePreviewAttribute != currentWebpagePreviewAttribute || disableUrlPreview {
|
||||
strongSelf.context.account.pendingUpdateMessageManager.add(messageId: editMessage.messageId, text: text.string, media: media, entities: entitiesAttribute, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -9197,7 +9234,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
strongSelf.updateItemNodesSearchTextHighlightStates()
|
||||
}
|
||||
}, navigateToMessage: { [weak self] messageId, dropStack, forceInCurrentChat, statusSubject in
|
||||
self?.navigateToMessage(from: nil, to: .id(messageId, nil), forceInCurrentChat: forceInCurrentChat, dropStack: dropStack, statusSubject: statusSubject)
|
||||
self?.navigateToMessage(from: nil, to: .id(messageId, NavigateToMessageParams(timestamp: nil, quote: nil)), forceInCurrentChat: forceInCurrentChat, dropStack: dropStack, statusSubject: statusSubject)
|
||||
}, navigateToChat: { [weak self] peerId in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
|
@ -9261,7 +9298,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
strongSelf.chatDisplayNode.collapseInput()
|
||||
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreview(nil) }
|
||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreviews([]) }
|
||||
})
|
||||
}
|
||||
}, nil)
|
||||
|
|
@ -12264,8 +12301,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
inScopeResult = result
|
||||
} else {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||
if let updatedUrlPreviewUrl = updatedUrlPreviewUrl, let webpage = result($0.urlPreview?.1) {
|
||||
return $0.updatedUrlPreview((updatedUrlPreviewUrl, webpage))
|
||||
if let updatedUrlPreviewUrl = updatedUrlPreviewUrl, let webpage = result($0.urlPreview?.webPage) {
|
||||
let updatedPreview = ChatPresentationInterfaceState.UrlPreview(
|
||||
url: updatedUrlPreviewUrl,
|
||||
webPage: webpage,
|
||||
positionBelowText: $0.urlPreview?.positionBelowText ?? true,
|
||||
largeMedia: $0.urlPreview?.largeMedia
|
||||
)
|
||||
return $0.updatedUrlPreview(updatedPreview)
|
||||
} else {
|
||||
return $0.updatedUrlPreview(nil)
|
||||
}
|
||||
|
|
@ -12275,8 +12318,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}))
|
||||
inScope = false
|
||||
if let inScopeResult = inScopeResult {
|
||||
if let updatedUrlPreviewUrl = updatedUrlPreviewUrl, let webpage = inScopeResult(updatedChatPresentationInterfaceState.urlPreview?.1) {
|
||||
updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedUrlPreview((updatedUrlPreviewUrl, webpage))
|
||||
if let updatedUrlPreviewUrl = updatedUrlPreviewUrl, let webpage = inScopeResult(updatedChatPresentationInterfaceState.urlPreview?.webPage) {
|
||||
let updatedPreview = ChatPresentationInterfaceState.UrlPreview(
|
||||
url: updatedUrlPreviewUrl,
|
||||
webPage: webpage,
|
||||
positionBelowText: updatedChatPresentationInterfaceState.urlPreview?.positionBelowText ?? true,
|
||||
largeMedia: updatedChatPresentationInterfaceState.urlPreview?.largeMedia
|
||||
)
|
||||
updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedUrlPreview(updatedPreview)
|
||||
} else {
|
||||
updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedUrlPreview(nil)
|
||||
}
|
||||
|
|
@ -12296,8 +12345,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
inScopeResult = result
|
||||
} else {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||
if let updatedEditingUrlPreviewUrl = updatedEditingUrlPreviewUrl, let webpage = result($0.editingUrlPreview?.1) {
|
||||
return $0.updatedEditingUrlPreview((updatedEditingUrlPreviewUrl, webpage))
|
||||
if let updatedEditingUrlPreviewUrl = updatedEditingUrlPreviewUrl, let webpage = result($0.editingUrlPreview?.webPage) {
|
||||
let updatedPreview = ChatPresentationInterfaceState.UrlPreview(
|
||||
url: updatedEditingUrlPreviewUrl,
|
||||
webPage: webpage,
|
||||
positionBelowText: $0.editingUrlPreview?.positionBelowText ?? true,
|
||||
largeMedia: $0.editingUrlPreview?.largeMedia
|
||||
)
|
||||
return $0.updatedEditingUrlPreview(updatedPreview)
|
||||
} else {
|
||||
return $0.updatedEditingUrlPreview(nil)
|
||||
}
|
||||
|
|
@ -12307,8 +12362,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}))
|
||||
inScope = false
|
||||
if let inScopeResult = inScopeResult {
|
||||
if let updatedEditingUrlPreviewUrl = updatedEditingUrlPreviewUrl, let webpage = inScopeResult(updatedChatPresentationInterfaceState.editingUrlPreview?.1) {
|
||||
updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedEditingUrlPreview((updatedEditingUrlPreviewUrl, webpage))
|
||||
if let updatedEditingUrlPreviewUrl = updatedEditingUrlPreviewUrl, let webpage = inScopeResult(updatedChatPresentationInterfaceState.editingUrlPreview?.webPage) {
|
||||
let updatedPreview = ChatPresentationInterfaceState.UrlPreview(
|
||||
url: updatedEditingUrlPreviewUrl,
|
||||
webPage: webpage,
|
||||
positionBelowText: updatedChatPresentationInterfaceState.editingUrlPreview?.positionBelowText ?? true,
|
||||
largeMedia: updatedChatPresentationInterfaceState.editingUrlPreview?.largeMedia
|
||||
)
|
||||
updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedEditingUrlPreview(updatedPreview)
|
||||
} else {
|
||||
updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedEditingUrlPreview(nil)
|
||||
}
|
||||
|
|
@ -15708,7 +15769,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
var interfaceState = interfaceState
|
||||
interfaceState = interfaceState.withUpdatedReplyMessageSubject(nil)
|
||||
interfaceState = interfaceState.withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: "")))
|
||||
interfaceState = interfaceState.withUpdatedComposeDisableUrlPreview(nil)
|
||||
interfaceState = interfaceState.withUpdatedComposeDisableUrlPreviews([])
|
||||
return interfaceState
|
||||
}
|
||||
}
|
||||
|
|
@ -16237,7 +16298,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}
|
||||
|
||||
func scrollToEndOfHistory() {
|
||||
let locationInput = ChatHistoryLocationInput(content: .Scroll(index: .upperBound, anchorIndex: .upperBound, sourceIndex: .lowerBound, scrollPosition: .top(0.0), animated: true, highlight: false), id: 0)
|
||||
let locationInput = ChatHistoryLocationInput(content: .Scroll(subject: MessageHistoryScrollToSubject(index: .upperBound, quote: nil), anchorIndex: .upperBound, sourceIndex: .lowerBound, scrollPosition: .top(0.0), animated: true, highlight: false), id: 0)
|
||||
|
||||
let historyView = preloadedChatHistoryViewForLocation(locationInput, context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||
let signal = historyView
|
||||
|
|
@ -16301,7 +16362,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}
|
||||
|
||||
func scrollToStartOfHistory() {
|
||||
let locationInput = ChatHistoryLocationInput(content: .Scroll(index: .lowerBound, anchorIndex: .lowerBound, sourceIndex: .upperBound, scrollPosition: .bottom(0.0), animated: true, highlight: false), id: 0)
|
||||
let locationInput = ChatHistoryLocationInput(content: .Scroll(subject: MessageHistoryScrollToSubject(index: .lowerBound, quote: nil), anchorIndex: .lowerBound, sourceIndex: .upperBound, scrollPosition: .bottom(0.0), animated: true, highlight: false), id: 0)
|
||||
|
||||
let historyView = preloadedChatHistoryViewForLocation(locationInput, context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||
let signal = historyView
|
||||
|
|
@ -16424,7 +16485,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
if let strongSelf = self {
|
||||
strongSelf.loadingMessage.set(.single(nil))
|
||||
if let messageId = messageId {
|
||||
strongSelf.navigateToMessage(from: nil, to: .id(messageId, nil), forceInCurrentChat: true)
|
||||
strongSelf.navigateToMessage(from: nil, to: .id(messageId, NavigateToMessageParams(timestamp: nil, quote: nil)), forceInCurrentChat: true)
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
|
@ -16461,7 +16522,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
if let strongSelf = self {
|
||||
strongSelf.loadingMessage.set(.single(nil))
|
||||
if let messageId = messageId {
|
||||
strongSelf.navigateToMessage(from: nil, to: .id(messageId, nil), forceInCurrentChat: true)
|
||||
strongSelf.navigateToMessage(from: nil, to: .id(messageId, NavigateToMessageParams(timestamp: nil, quote: nil)), forceInCurrentChat: true)
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
|
@ -16718,7 +16779,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
chatLocation = .replyThread(ChatReplyThreadMessage(messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false))
|
||||
}
|
||||
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: chatLocation, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil), keepStack: .always))
|
||||
var quote: String?
|
||||
if case let .id(_, params) = messageLocation {
|
||||
quote = params.quote
|
||||
}
|
||||
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: chatLocation, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: quote), timecode: nil), keepStack: .always))
|
||||
}
|
||||
})
|
||||
} else if forceInCurrentChat {
|
||||
|
|
@ -16745,7 +16811,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
delayCompletion = false
|
||||
}
|
||||
|
||||
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: message.index, animated: animated, scrollPosition: scrollPosition)
|
||||
var quote: String?
|
||||
if case let .id(_, params) = messageLocation {
|
||||
quote = params.quote
|
||||
}
|
||||
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: message.index, animated: animated, quote: quote, scrollPosition: scrollPosition)
|
||||
|
||||
if delayCompletion {
|
||||
Queue.mainQueue().after(0.25, {
|
||||
|
|
@ -16757,14 +16827,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
})
|
||||
}
|
||||
|
||||
if case let .id(_, maybeTimecode) = messageLocation, let timecode = maybeTimecode {
|
||||
if case let .id(_, params) = messageLocation, let timecode = params.timestamp {
|
||||
let _ = self.controllerInteraction?.openMessage(message, .timecode(timecode))
|
||||
}
|
||||
} else if case let .index(index) = messageLocation, index.id.id == 0, index.timestamp > 0, case .scheduledMessages = self.presentationInterfaceState.subject {
|
||||
self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: index, animated: animated, scrollPosition: scrollPosition)
|
||||
} else {
|
||||
if case let .id(messageId, maybeTimecode) = messageLocation, let timecode = maybeTimecode {
|
||||
self.scheduledScrollToMessageId = (messageId, timecode)
|
||||
if case let .id(messageId, params) = messageLocation, params.timestamp != nil {
|
||||
self.scheduledScrollToMessageId = (messageId, params)
|
||||
}
|
||||
self.loadingMessage.set(.single(statusSubject) |> delay(0.1, queue: .mainQueue()))
|
||||
let searchLocation: ChatHistoryInitialSearchLocation
|
||||
|
|
@ -16780,7 +16850,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
searchLocation = .index(.absoluteUpperBound())
|
||||
}
|
||||
}
|
||||
let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||
let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: searchLocation, quote: nil), count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||
|
||||
let signal = historyView
|
||||
|> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in
|
||||
|
|
@ -16877,7 +16947,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
self.historyNavigationStack.add(fromIndex)
|
||||
}
|
||||
self.loadingMessage.set(.single(statusSubject) |> delay(0.1, queue: .mainQueue()))
|
||||
let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(location: searchLocation, count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||
|
||||
var quote: String?
|
||||
if case let .id(_, params) = messageLocation {
|
||||
quote = params.quote
|
||||
}
|
||||
|
||||
let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: searchLocation, quote: quote), count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: [])
|
||||
let signal = historyView
|
||||
|> mapToSignal { historyView -> Signal<MessageIndex?, NoError> in
|
||||
switch historyView {
|
||||
|
|
@ -17789,7 +17865,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
if case .peer(peerId.id) = strongSelf.chatLocation {
|
||||
if let subject = subject, case let .message(messageSubject, _, timecode) = subject {
|
||||
if case let .id(messageId) = messageSubject {
|
||||
strongSelf.navigateToMessage(from: sourceMessageId, to: .id(messageId, timecode))
|
||||
strongSelf.navigateToMessage(from: sourceMessageId, to: .id(messageId, NavigateToMessageParams(timestamp: timecode, quote: nil)))
|
||||
}
|
||||
} else {
|
||||
self?.playShakeAnimation()
|
||||
|
|
@ -17863,6 +17939,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
func openUrl(_ url: String, concealed: Bool, forceExternal: Bool = false, skipUrlAuth: Bool = false, skipConcealedAlert: Bool = false, message: Message? = nil, commit: @escaping () -> Void = {}) {
|
||||
self.commitPurposefulAction()
|
||||
|
||||
if let message, let webpage = message.media.first(where: { $0 is TelegramMediaWebpage }) as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, content.url == url {
|
||||
if let navigationController = self.navigationController as? NavigationController {
|
||||
self.context.sharedContext.openChatInstantPage(context: self.context, message: message, sourcePeerType: nil, navigationController: navigationController)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let _ = self.presentVoiceMessageDiscardAlert(action: {
|
||||
openUserGeneratedUrl(context: self.context, peerId: self.peerView?.peerId, url: url, concealed: concealed, skipUrlAuth: skipUrlAuth, skipConcealedAlert: skipConcealedAlert, present: { [weak self] c in
|
||||
self?.present(c, in: .window(.root))
|
||||
|
|
@ -18569,7 +18652,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
return updatedState
|
||||
})
|
||||
|
||||
strongSelf.navigateToMessage(messageLocation: .id(message.id, nil), animated: true)
|
||||
strongSelf.navigateToMessage(messageLocation: .id(message.id, NavigateToMessageParams(timestamp: nil, quote: nil)), animated: true)
|
||||
}
|
||||
} else {
|
||||
strongSelf.scrollToEndOfHistory()
|
||||
|
|
@ -18591,7 +18674,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
})
|
||||
|
||||
if let lastMessage = lastMessage {
|
||||
strongSelf.navigateToMessage(messageLocation: .id(lastMessage.id, nil), animated: true)
|
||||
strongSelf.navigateToMessage(messageLocation: .id(lastMessage.id, NavigateToMessageParams(timestamp: nil, quote: nil)), animated: true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -18630,7 +18713,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
})
|
||||
|
||||
if let updatedReplyMessageSubject {
|
||||
strongSelf.navigateToMessage(messageLocation: .id(updatedReplyMessageSubject.messageId, nil), animated: true)
|
||||
strongSelf.navigateToMessage(messageLocation: .id(updatedReplyMessageSubject.messageId, NavigateToMessageParams(timestamp: nil, quote: nil)), animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -528,32 +528,21 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||
|
||||
var media: [Media] = []
|
||||
if case let .Loaded(content) = options.webpage.content {
|
||||
var displayOptions: TelegramMediaWebpageDisplayOptions = .default
|
||||
|
||||
if options.linkBelowText {
|
||||
displayOptions.position = .belowText
|
||||
} else {
|
||||
displayOptions.position = .aboveText
|
||||
}
|
||||
|
||||
if options.largeMedia {
|
||||
displayOptions.largeMedia = true
|
||||
} else {
|
||||
displayOptions.largeMedia = false
|
||||
}
|
||||
|
||||
media.append(TelegramMediaWebpage(webpageId: options.webpage.webpageId, content: .Loaded(content.withDisplayOptions(displayOptions))))
|
||||
media.append(TelegramMediaWebpage(webpageId: options.webpage.webpageId, content: .Loaded(content)))
|
||||
}
|
||||
|
||||
var attributes: [MessageAttribute] = []
|
||||
|
||||
attributes.append(TextEntitiesMessageAttribute(entities: options.messageEntities))
|
||||
|
||||
attributes.append(WebpagePreviewMessageAttribute(leadingPreview: !options.linkBelowText, forceLargeMedia: options.largeMedia, isManuallyAdded: true))
|
||||
|
||||
if let replyMessage {
|
||||
associatedMessages[replyMessage.id] = replyMessage
|
||||
|
||||
var mappedQuote: EngineMessageReplyQuote?
|
||||
if let quote = options.replyQuote {
|
||||
mappedQuote = EngineMessageReplyQuote(text: quote, entities: [])
|
||||
mappedQuote = EngineMessageReplyQuote(text: quote, entities: [], media: nil)
|
||||
}
|
||||
|
||||
attributes.append(ReplyMessageAttribute(messageId: replyMessage.id, threadMessageId: nil, quote: mappedQuote))
|
||||
|
|
@ -3411,10 +3400,13 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||
attributes.append(TextEntitiesMessageAttribute(entities: entities))
|
||||
}
|
||||
var webpage: TelegramMediaWebpage?
|
||||
if self.chatPresentationInterfaceState.interfaceState.composeDisableUrlPreview != nil {
|
||||
attributes.append(OutgoingContentInfoMessageAttribute(flags: [.disableLinkPreviews]))
|
||||
} else {
|
||||
webpage = self.chatPresentationInterfaceState.urlPreview?.1
|
||||
if let urlPreview = self.chatPresentationInterfaceState.urlPreview {
|
||||
if self.chatPresentationInterfaceState.interfaceState.composeDisableUrlPreviews.contains(urlPreview.url) {
|
||||
attributes.append(OutgoingContentInfoMessageAttribute(flags: [.disableLinkPreviews]))
|
||||
} else {
|
||||
webpage = urlPreview.webPage
|
||||
attributes.append(WebpagePreviewMessageAttribute(leadingPreview: !urlPreview.positionBelowText, forceLargeMedia: urlPreview.largeMedia, isManuallyAdded: true))
|
||||
}
|
||||
}
|
||||
|
||||
var bubbleUpEmojiOrStickersets: [ItemCollectionId] = []
|
||||
|
|
@ -3488,7 +3480,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||
|
||||
strongSelf.ignoreUpdateHeight = true
|
||||
textInputPanelNode.text = ""
|
||||
strongSelf.requestUpdateChatInterfaceState(.immediate, true, { $0.withUpdatedReplyMessageSubject(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeDisableUrlPreview(nil) })
|
||||
strongSelf.requestUpdateChatInterfaceState(.immediate, true, { $0.withUpdatedReplyMessageSubject(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeDisableUrlPreviews([]) })
|
||||
strongSelf.ignoreUpdateHeight = false
|
||||
}
|
||||
}, usedCorrelationId)
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public enum ChatHistoryListMode: Equatable {
|
|||
enum ChatHistoryViewScrollPosition {
|
||||
case unread(index: MessageIndex)
|
||||
case positionRestoration(index: MessageIndex, relativeOffset: CGFloat)
|
||||
case index(index: MessageHistoryAnchorIndex, position: ListViewScrollPosition, directionHint: ListViewScrollToItemDirectionHint, animated: Bool, highlight: Bool, displayLink: Bool)
|
||||
case index(subject: MessageHistoryScrollToSubject, position: ListViewScrollPosition, directionHint: ListViewScrollToItemDirectionHint, animated: Bool, highlight: Bool, displayLink: Bool)
|
||||
}
|
||||
|
||||
enum ChatHistoryViewUpdateType {
|
||||
|
|
@ -125,7 +125,7 @@ struct ChatHistoryViewTransition {
|
|||
var cachedData: CachedPeerData?
|
||||
var cachedDataMessages: [MessageId: Message]?
|
||||
var readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?
|
||||
var scrolledToIndex: MessageHistoryAnchorIndex?
|
||||
var scrolledToIndex: MessageHistoryScrollToSubject?
|
||||
var scrolledToSomeIndex: Bool
|
||||
var animateIn: Bool
|
||||
var reason: ChatHistoryViewTransitionReason
|
||||
|
|
@ -145,7 +145,7 @@ struct ChatHistoryListViewTransition {
|
|||
var cachedData: CachedPeerData?
|
||||
var cachedDataMessages: [MessageId: Message]?
|
||||
var readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?
|
||||
var scrolledToIndex: MessageHistoryAnchorIndex?
|
||||
var scrolledToIndex: MessageHistoryScrollToSubject?
|
||||
var scrolledToSomeIndex: Bool
|
||||
var peerType: MediaAutoDownloadPeerType
|
||||
var networkType: MediaAutoDownloadNetworkType
|
||||
|
|
@ -206,7 +206,7 @@ extension ListMessageItemInteraction {
|
|||
}, toggleMessagesSelection: { messageId, selected in
|
||||
controllerInteraction.toggleMessagesSelection(messageId, selected)
|
||||
}, openUrl: { url, param1, param2, message in
|
||||
controllerInteraction.openUrl(url, param1, param2, message)
|
||||
controllerInteraction.openUrl(url, param1, param2, message, nil)
|
||||
}, openInstantPage: { message, data in
|
||||
controllerInteraction.openInstantPage(message, data)
|
||||
}, longTap: { action, message in
|
||||
|
|
@ -381,10 +381,14 @@ private extension ChatHistoryLocationInput {
|
|||
switch self.content {
|
||||
case .Navigation(index: .upperBound, anchorIndex: .upperBound, count: _, highlight: _):
|
||||
return true
|
||||
case .Scroll(index: .upperBound, anchorIndex: .upperBound, sourceIndex: _, scrollPosition: _, animated: _, highlight: _):
|
||||
case let .Scroll(subject, anchorIndex, _, _, _, _):
|
||||
if case .upperBound = anchorIndex, case .upperBound = subject.index {
|
||||
return true
|
||||
default:
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -532,7 +536,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||
private var maxVisibleMessageIndexReported: MessageIndex?
|
||||
var maxVisibleMessageIndexUpdated: ((MessageIndex) -> Void)?
|
||||
|
||||
var scrolledToIndex: ((MessageHistoryAnchorIndex, Bool) -> Void)?
|
||||
var scrolledToIndex: ((MessageHistoryScrollToSubject, Bool) -> Void)?
|
||||
var scrolledToSomeIndex: (() -> Void)?
|
||||
var beganDragging: (() -> Void)?
|
||||
|
||||
|
|
@ -812,9 +816,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||
initialSearchLocation = .index(MessageIndex.absoluteUpperBound())
|
||||
}
|
||||
}
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: initialSearchLocation, count: historyMessageCount, highlight: highlight != nil), id: 0)
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: initialSearchLocation, quote: highlight?.quote), count: historyMessageCount, highlight: highlight != nil), id: 0)
|
||||
} else if let subject = subject, case let .pinnedMessages(maybeMessageId) = subject, let messageId = maybeMessageId {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: historyMessageCount, highlight: true), id: 0)
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(messageId), quote: nil), count: historyMessageCount, highlight: true), id: 0)
|
||||
} else {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Initial(count: historyMessageCount), id: 0)
|
||||
}
|
||||
|
|
@ -1092,7 +1096,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||
|
||||
let scrollPosition: ChatHistoryViewScrollPosition?
|
||||
if isFirstTime, let messageIndex = messages.first(where: { $0.id == at })?.index {
|
||||
scrollPosition = .index(index: .message(messageIndex), position: .center(.bottom), directionHint: .Down, animated: false, highlight: false, displayLink: false)
|
||||
scrollPosition = .index(subject: MessageHistoryScrollToSubject(index: .message(messageIndex), quote: nil), position: .center(.bottom), directionHint: .Down, animated: false, highlight: false, displayLink: false)
|
||||
isFirstTime = false
|
||||
} else {
|
||||
scrollPosition = nil
|
||||
|
|
@ -1370,9 +1374,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||
initialSearchLocation = .index(.absoluteUpperBound())
|
||||
}
|
||||
}
|
||||
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: initialSearchLocation, count: historyMessageCount, highlight: highlight != nil), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
||||
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: initialSearchLocation, quote: nil), count: historyMessageCount, highlight: highlight != nil), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
||||
} else if let subject = subject, case let .pinnedMessages(maybeMessageId) = subject, let messageId = maybeMessageId {
|
||||
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: historyMessageCount, highlight: true), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
||||
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(messageId), quote: nil), count: historyMessageCount, highlight: true), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
||||
} else if var chatHistoryLocation = strongSelf.chatHistoryLocationValue {
|
||||
chatHistoryLocation.id += 1
|
||||
strongSelf.chatHistoryLocationValue = chatHistoryLocation
|
||||
|
|
@ -1508,10 +1512,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||
|
||||
if scrollPosition == nil, let originalScrollPosition = originalScrollPosition {
|
||||
switch originalScrollPosition {
|
||||
case let .index(index, position, _, _, highlight, displayLink):
|
||||
if case .upperBound = index {
|
||||
case let .index(subject, position, _, _, highlight, displayLink):
|
||||
if case .upperBound = subject.index {
|
||||
if let previous = previous, previous.filteredEntries.isEmpty {
|
||||
updatedScrollPosition = .index(index: index, position: position, directionHint: .Down, animated: false, highlight: highlight, displayLink: displayLink)
|
||||
updatedScrollPosition = .index(subject: subject, position: position, directionHint: .Down, animated: false, highlight: highlight, displayLink: displayLink)
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
|
@ -1558,7 +1562,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||
if let strongSelf = self, case .default = source {
|
||||
strongSelf.toLang = translateToLanguage
|
||||
if strongSelf.appliedScrollToMessageId == nil, let scrollToMessageId = scrollToMessageId {
|
||||
updatedScrollPosition = .index(index: .message(scrollToMessageId), position: .center(.top), directionHint: .Up, animated: true, highlight: false, displayLink: true)
|
||||
updatedScrollPosition = .index(subject: MessageHistoryScrollToSubject(index: .message(scrollToMessageId), quote: nil), position: .center(.top), directionHint: .Up, animated: true, highlight: false, displayLink: true)
|
||||
scrollAnimationCurve = .Spring(duration: 0.4)
|
||||
} else {
|
||||
let wasPlaying = strongSelf.appliedPlayingMessageId != nil
|
||||
|
|
@ -1588,7 +1592,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||
}
|
||||
})
|
||||
if currentIsVisible && nextIsVisible && currentlyPlayingVideo {
|
||||
updatedScrollPosition = .index(index: .message(currentlyPlayingMessageId), position: .center(.bottom), directionHint: .Up, animated: true, highlight: true, displayLink: true)
|
||||
updatedScrollPosition = .index(subject: MessageHistoryScrollToSubject(index: .message(currentlyPlayingMessageId), quote: nil), position: .center(.bottom), directionHint: .Up, animated: true, highlight: true, displayLink: true)
|
||||
scrollAnimationCurve = .Spring(duration: 0.4)
|
||||
}
|
||||
}
|
||||
|
|
@ -1633,7 +1637,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||
}
|
||||
|
||||
if fillsScreen, let firstNonAdIndex = firstNonAdIndex, previousNumAds == 0, updatedNumAds != 0 {
|
||||
updatedScrollPosition = .index(index: .message(firstNonAdIndex), position: .top(0.0), directionHint: .Up, animated: false, highlight: false, displayLink: false)
|
||||
updatedScrollPosition = .index(subject: MessageHistoryScrollToSubject(index: .message(firstNonAdIndex), quote: nil), position: .top(0.0), directionHint: .Up, animated: false, highlight: false, displayLink: false)
|
||||
disableAnimations = true
|
||||
}
|
||||
}
|
||||
|
|
@ -2573,7 +2577,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||
currentMessage = messages.first?.0
|
||||
}
|
||||
if let message = currentMessage, let _ = self.anchorMessageInCurrentHistoryView() {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(index: .message(message.index), anchorIndex: .message(message.index), sourceIndex: .upperBound, scrollPosition: .bottom(0.0), animated: true, highlight: false), id: self.takeNextHistoryLocationId())
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(subject: MessageHistoryScrollToSubject(index: .message(message.index), quote: nil), anchorIndex: .message(message.index), sourceIndex: .upperBound, scrollPosition: .bottom(0.0), animated: true, highlight: false), id: self.takeNextHistoryLocationId())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2602,14 +2606,14 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||
}
|
||||
|
||||
if let currentMessage = currentMessage {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(index: .message(currentMessage.index), anchorIndex: .message(currentMessage.index), sourceIndex: .upperBound, scrollPosition: .top(0.0), animated: true, highlight: true), id: self.takeNextHistoryLocationId())
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(subject: MessageHistoryScrollToSubject(index: .message(currentMessage.index), quote: nil), anchorIndex: .message(currentMessage.index), sourceIndex: .upperBound, scrollPosition: .top(0.0), animated: true, highlight: true), id: self.takeNextHistoryLocationId())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func scrollToStartOfHistory() {
|
||||
self.beganDragging?()
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(index: .lowerBound, anchorIndex: .lowerBound, sourceIndex: .upperBound, scrollPosition: .bottom(0.0), animated: true, highlight: false), id: self.takeNextHistoryLocationId())
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(subject: MessageHistoryScrollToSubject(index: .lowerBound, quote: nil), anchorIndex: .lowerBound, sourceIndex: .upperBound, scrollPosition: .bottom(0.0), animated: true, highlight: false), id: self.takeNextHistoryLocationId())
|
||||
}
|
||||
|
||||
public func scrollToEndOfHistory() {
|
||||
|
|
@ -2618,13 +2622,13 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||
case let .known(value) where value <= CGFloat.ulpOfOne:
|
||||
break
|
||||
default:
|
||||
let locationInput = ChatHistoryLocationInput(content: .Scroll(index: .upperBound, anchorIndex: .upperBound, sourceIndex: .lowerBound, scrollPosition: .top(0.0), animated: true, highlight: false), id: self.takeNextHistoryLocationId())
|
||||
let locationInput = ChatHistoryLocationInput(content: .Scroll(subject: MessageHistoryScrollToSubject(index: .upperBound, quote: nil), anchorIndex: .upperBound, sourceIndex: .lowerBound, scrollPosition: .top(0.0), animated: true, highlight: false), id: self.takeNextHistoryLocationId())
|
||||
self.chatHistoryLocationValue = locationInput
|
||||
}
|
||||
}
|
||||
|
||||
public func scrollToMessage(from fromIndex: MessageIndex, to toIndex: MessageIndex, animated: Bool, highlight: Bool = true, scrollPosition: ListViewScrollPosition = .center(.bottom)) {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(index: .message(toIndex), anchorIndex: .message(toIndex), sourceIndex: .message(fromIndex), scrollPosition: scrollPosition, animated: animated, highlight: highlight), id: self.takeNextHistoryLocationId())
|
||||
public func scrollToMessage(from fromIndex: MessageIndex, to toIndex: MessageIndex, animated: Bool, highlight: Bool = true, quote: String? = nil, scrollPosition: ListViewScrollPosition = .center(.bottom)) {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(subject: MessageHistoryScrollToSubject(index: .message(toIndex), quote: quote), anchorIndex: .message(toIndex), sourceIndex: .message(fromIndex), scrollPosition: scrollPosition, animated: animated, highlight: highlight), id: self.takeNextHistoryLocationId())
|
||||
}
|
||||
|
||||
public func anchorMessageInCurrentHistoryView() -> Message? {
|
||||
|
|
|
|||
|
|
@ -41,9 +41,9 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, ignoreMess
|
|||
if scheduled {
|
||||
var first = true
|
||||
var chatScrollPosition: ChatHistoryViewScrollPosition?
|
||||
if case let .Scroll(index, _, sourceIndex, position, animated, highlight) = location.content {
|
||||
let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up
|
||||
chatScrollPosition = .index(index: index, position: position, directionHint: directionHint, animated: animated, highlight: highlight, displayLink: false)
|
||||
if case let .Scroll(subject, _, sourceIndex, position, animated, highlight) = location.content {
|
||||
let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > subject.index ? .Down : .Up
|
||||
chatScrollPosition = .index(subject: subject, position: position, directionHint: directionHint, animated: animated, highlight: highlight, displayLink: false)
|
||||
}
|
||||
return account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), additionalData: additionalData)
|
||||
|> map { view, updateType, initialData -> ChatHistoryViewUpdate in
|
||||
|
|
@ -112,7 +112,7 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, ignoreMess
|
|||
|
||||
if tagMask == nil, case let .replyThread(message) = chatLocation, message.isForumPost, view.maxReadIndex == nil {
|
||||
if case let .message(index) = view.anchorIndex {
|
||||
scrollPosition = .index(index: .message(index), position: .bottom(0.0), directionHint: .Up, animated: false, highlight: false, displayLink: false)
|
||||
scrollPosition = .index(subject: MessageHistoryScrollToSubject(index: .message(index), quote: nil), position: .bottom(0.0), directionHint: .Up, animated: false, highlight: false, displayLink: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -174,12 +174,12 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, ignoreMess
|
|||
return .HistoryView(view: view, type: .Initial(fadeIn: fadeIn), scrollPosition: scrollPosition, flashIndicators: false, originalScrollPosition: nil, initialData: ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData), id: location.id)
|
||||
}
|
||||
}
|
||||
case let .InitialSearch(searchLocation, count, highlight):
|
||||
case let .InitialSearch(searchLocationSubject, count, highlight):
|
||||
var preloaded = false
|
||||
var fadeIn = false
|
||||
|
||||
let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>
|
||||
switch searchLocation {
|
||||
switch searchLocationSubject.location {
|
||||
case let .index(index):
|
||||
signal = account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, index: .message(index), anchorIndex: .message(index), count: count, ignoreRelatedChats: ignoreRelatedChats, fixedCombinedReadStates: nil, tagMask: tagMask, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, orderStatistics: orderStatistics, additionalData: additionalData)
|
||||
case let .id(id):
|
||||
|
|
@ -226,7 +226,8 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, ignoreMess
|
|||
}
|
||||
|
||||
preloaded = true
|
||||
return .HistoryView(view: view, type: reportUpdateType, scrollPosition: .index(index: anchorIndex, position: .center(.bottom), directionHint: .Down, animated: false, highlight: highlight, displayLink: false), flashIndicators: false, originalScrollPosition: nil, initialData: ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData), id: location.id)
|
||||
|
||||
return .HistoryView(view: view, type: reportUpdateType, scrollPosition: .index(subject: MessageHistoryScrollToSubject(index: anchorIndex, quote: searchLocationSubject.quote), position: .center(.bottom), directionHint: .Down, animated: false, highlight: highlight, displayLink: false), flashIndicators: false, originalScrollPosition: nil, initialData: ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData), id: location.id)
|
||||
}
|
||||
}
|
||||
case let .Navigation(index, anchorIndex, count, _):
|
||||
|
|
@ -243,11 +244,11 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, ignoreMess
|
|||
}
|
||||
return .HistoryView(view: view, type: .Generic(type: genericType), scrollPosition: nil, flashIndicators: false, originalScrollPosition: nil, initialData: ChatHistoryCombinedInitialData(initialData: initialData, buttonKeyboardMessage: view.topTaggedMessages.first, cachedData: cachedData, cachedDataMessages: cachedDataMessages, readStateData: readStateData), id: location.id)
|
||||
}
|
||||
case let .Scroll(index, anchorIndex, sourceIndex, scrollPosition, animated, highlight):
|
||||
let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up
|
||||
let chatScrollPosition = ChatHistoryViewScrollPosition.index(index: index, position: scrollPosition, directionHint: directionHint, animated: animated, highlight: highlight, displayLink: false)
|
||||
case let .Scroll(subject, anchorIndex, sourceIndex, scrollPosition, animated, highlight):
|
||||
let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > subject.index ? .Down : .Up
|
||||
let chatScrollPosition = ChatHistoryViewScrollPosition.index(subject: subject, position: scrollPosition, directionHint: directionHint, animated: animated, highlight: highlight, displayLink: false)
|
||||
var first = true
|
||||
return account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, index: index, anchorIndex: anchorIndex, count: 128, ignoreRelatedChats: ignoreRelatedChats, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, orderStatistics: orderStatistics, additionalData: additionalData)
|
||||
return account.viewTracker.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, index: subject.index, anchorIndex: anchorIndex, count: 128, ignoreRelatedChats: ignoreRelatedChats, fixedCombinedReadStates: fixedCombinedReadStates, tagMask: tagMask, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, orderStatistics: orderStatistics, additionalData: additionalData)
|
||||
|> map { view, updateType, initialData -> ChatHistoryViewUpdate in
|
||||
let (cachedData, cachedDataMessages, readStateData) = extractAdditionalData(view: view, chatLocation: chatLocation)
|
||||
|
||||
|
|
@ -359,7 +360,7 @@ func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThrea
|
|||
case .automatic:
|
||||
if let atMessageId = atMessageId {
|
||||
input = ChatHistoryLocationInput(
|
||||
content: .InitialSearch(location: .id(atMessageId), count: 40, highlight: true),
|
||||
content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(atMessageId), quote: nil), count: 40, highlight: true),
|
||||
id: 0
|
||||
)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -28,14 +28,14 @@ func accessoryPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceS
|
|||
}
|
||||
|
||||
if let editMessage = chatPresentationInterfaceState.interfaceState.editMessage {
|
||||
if let editingUrlPreview = chatPresentationInterfaceState.editingUrlPreview, editMessage.disableUrlPreview != editingUrlPreview.0 {
|
||||
if let editingUrlPreview = chatPresentationInterfaceState.editingUrlPreview, !editMessage.disableUrlPreviews.contains(editingUrlPreview.url) {
|
||||
if let previewPanelNode = currentPanel as? WebpagePreviewAccessoryPanelNode {
|
||||
previewPanelNode.interfaceInteraction = interfaceInteraction
|
||||
previewPanelNode.replaceWebpage(url: editingUrlPreview.0, webpage: editingUrlPreview.1)
|
||||
previewPanelNode.replaceWebpage(url: editingUrlPreview.url, webpage: editingUrlPreview.webPage)
|
||||
previewPanelNode.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
||||
return previewPanelNode
|
||||
} else {
|
||||
let panelNode = WebpagePreviewAccessoryPanelNode(context: context, url: editingUrlPreview.0, webpage: editingUrlPreview.1, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
||||
let panelNode = WebpagePreviewAccessoryPanelNode(context: context, url: editingUrlPreview.url, webpage: editingUrlPreview.webPage, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
||||
panelNode.interfaceInteraction = interfaceInteraction
|
||||
return panelNode
|
||||
}
|
||||
|
|
@ -50,14 +50,14 @@ func accessoryPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceS
|
|||
panelNode.interfaceInteraction = interfaceInteraction
|
||||
return panelNode
|
||||
}
|
||||
} else if let urlPreview = chatPresentationInterfaceState.urlPreview, chatPresentationInterfaceState.interfaceState.composeDisableUrlPreview != urlPreview.0 {
|
||||
} else if let urlPreview = chatPresentationInterfaceState.urlPreview, !chatPresentationInterfaceState.interfaceState.composeDisableUrlPreviews.contains(urlPreview.url) {
|
||||
if let previewPanelNode = currentPanel as? WebpagePreviewAccessoryPanelNode {
|
||||
previewPanelNode.interfaceInteraction = interfaceInteraction
|
||||
previewPanelNode.replaceWebpage(url: urlPreview.0, webpage: urlPreview.1)
|
||||
previewPanelNode.replaceWebpage(url: urlPreview.url, webpage: urlPreview.webPage)
|
||||
previewPanelNode.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
||||
return previewPanelNode
|
||||
} else {
|
||||
let panelNode = WebpagePreviewAccessoryPanelNode(context: context, url: urlPreview.0, webpage: urlPreview.1, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
||||
let panelNode = WebpagePreviewAccessoryPanelNode(context: context, url: urlPreview.url, webpage: urlPreview.webPage, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
||||
panelNode.interfaceInteraction = interfaceInteraction
|
||||
return panelNode
|
||||
}
|
||||
|
|
|
|||
|
|
@ -382,7 +382,18 @@ func updatedChatEditInterfaceMessageState(state: ChatPresentationInterfaceState,
|
|||
var updated = state
|
||||
for media in message.media {
|
||||
if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
||||
updated = updated.updatedEditingUrlPreview((content.url, webpage))
|
||||
let attribute = message.attributes.first(where: { $0 is WebpagePreviewMessageAttribute }) as? WebpagePreviewMessageAttribute
|
||||
var positionBelowText = true
|
||||
if let leadingPreview = attribute?.leadingPreview {
|
||||
positionBelowText = !leadingPreview
|
||||
}
|
||||
let updatedPreview = ChatPresentationInterfaceState.UrlPreview(
|
||||
url: content.url,
|
||||
webPage: webpage,
|
||||
positionBelowText: positionBelowText,
|
||||
largeMedia: attribute?.forceLargeMedia
|
||||
)
|
||||
updated = updated.updatedEditingUrlPreview(updatedPreview)
|
||||
}
|
||||
}
|
||||
var isPlaintext = true
|
||||
|
|
|
|||
|
|
@ -477,27 +477,27 @@ func searchQuerySuggestionResultStateForChatInterfacePresentationState(_ chatPre
|
|||
|
||||
private let dataDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.link]).rawValue)
|
||||
|
||||
func detectUrl(_ inputText: NSAttributedString?) -> String? {
|
||||
var detectedUrl: String?
|
||||
func detectUrls(_ inputText: NSAttributedString?) -> [String] {
|
||||
var detectedUrls: [String] = []
|
||||
if let text = inputText, let dataDetector = dataDetector {
|
||||
let utf16 = text.string.utf16
|
||||
|
||||
let nsRange = NSRange(location: 0, length: utf16.count)
|
||||
let matches = dataDetector.matches(in: text.string, options: [], range: nsRange)
|
||||
if let match = matches.first {
|
||||
for match in matches {
|
||||
let urlText = (text.string as NSString).substring(with: match.range)
|
||||
detectedUrl = urlText
|
||||
detectedUrls.append(urlText)
|
||||
}
|
||||
|
||||
if detectedUrl == nil {
|
||||
inputText?.enumerateAttribute(ChatTextInputAttributes.textUrl, in: nsRange, options: [], using: { value, range, stop in
|
||||
if let value = value as? ChatTextInputTextUrlAttribute {
|
||||
detectedUrl = value.url
|
||||
inputText?.enumerateAttribute(ChatTextInputAttributes.textUrl, in: nsRange, options: [], using: { value, range, stop in
|
||||
if let value = value as? ChatTextInputTextUrlAttribute {
|
||||
if !detectedUrls.contains(value.url) {
|
||||
detectedUrls.append(value.url)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return detectedUrl
|
||||
return detectedUrls
|
||||
}
|
||||
|
||||
func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: AccountContext, currentQuery: String?) -> (String?, Signal<(TelegramMediaWebpage?) -> TelegramMediaWebpage?, NoError>)? {
|
||||
|
|
@ -509,7 +509,7 @@ func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: Acco
|
|||
}
|
||||
}
|
||||
if let _ = dataDetector {
|
||||
let detectedUrl = detectUrl(inputText)
|
||||
let detectedUrl = detectUrls(inputText).first
|
||||
if detectedUrl != currentQuery {
|
||||
if let detectedUrl = detectedUrl {
|
||||
return (detectedUrl, webpagePreview(account: context.account, url: detectedUrl) |> map { value in
|
||||
|
|
|
|||
|
|
@ -858,7 +858,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||
if url.hasPrefix("tg://") {
|
||||
isConcealed = false
|
||||
}
|
||||
controllerInteraction.openUrl(url, isConcealed, nil, nil)
|
||||
controllerInteraction.openUrl(url, isConcealed, nil, nil, nil)
|
||||
case .requestMap:
|
||||
controllerInteraction.shareCurrentLocation()
|
||||
case .requestPhone:
|
||||
|
|
|
|||
|
|
@ -3665,14 +3665,27 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
|||
return nil
|
||||
}
|
||||
|
||||
var suggestedActionCounter: Int = 0
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
func chatInputTextNodeMenu(forTextRange textRange: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu {
|
||||
guard let editableTextNode = self.textInputNode else {
|
||||
return UIMenu(children: [])
|
||||
}
|
||||
|
||||
/*if "".isEmpty {
|
||||
let index = self.suggestedActionCounter % suggestedActions.count
|
||||
self.suggestedActionCounter += 1
|
||||
print("action index: \(index)")
|
||||
return UIMenu(children: [suggestedActions[index]])
|
||||
}*/
|
||||
|
||||
var actions = suggestedActions
|
||||
|
||||
if let index = actions.firstIndex(where: { $0.description.contains("identifier = com.apple.menu.replace;") }) {
|
||||
actions.remove(at: index)
|
||||
}
|
||||
|
||||
if editableTextNode.attributedText == nil || editableTextNode.attributedText!.length == 0 || editableTextNode.selectedRange.length == 0 {
|
||||
|
||||
} else {
|
||||
|
|
@ -3732,7 +3745,11 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
|||
] as [UIAction])
|
||||
|
||||
let formatMenu = UIMenu(title: self.strings?.TextFormat_Format ?? "Format", image: nil, children: children)
|
||||
actions.insert(formatMenu, at: 2)
|
||||
if let index = actions.firstIndex(where: { $0.description.contains("identifier = com.apple.menu.format;") }) {
|
||||
actions[index] = formatMenu
|
||||
} else {
|
||||
actions.insert(formatMenu, at: 2)
|
||||
}
|
||||
}
|
||||
return UIMenu(children: actions)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import LegacyInstantVideoController
|
|||
import StoryContainerScreen
|
||||
import CameraScreen
|
||||
import MediaEditorScreen
|
||||
import ChatControllerInteraction
|
||||
|
||||
public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParams) {
|
||||
if case let .peer(peer) = params.chatLocation, case let .channel(channel) = peer, channel.flags.contains(.isForum) {
|
||||
|
|
@ -76,7 +77,7 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
|
|||
if case let .id(messageId) = messageSubject {
|
||||
let navigationController = params.navigationController
|
||||
let animated = params.animated
|
||||
controller.navigateToMessage(messageLocation: .id(messageId, timecode), animated: isFirst, completion: { [weak navigationController, weak controller] in
|
||||
controller.navigateToMessage(messageLocation: .id(messageId, NavigateToMessageParams(timestamp: timecode, quote: nil)), animated: isFirst, completion: { [weak navigationController, weak controller] in
|
||||
if let navigationController = navigationController, let controller = controller {
|
||||
let _ = navigationController.popToViewController(controller, animated: animated)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
|||
}, updateMessageReaction: { _, _ in
|
||||
}, activateMessagePinch: { _ in
|
||||
}, openMessageContextActions: { _, _, _, _ in
|
||||
}, navigateToMessage: { _, _ in
|
||||
}, navigateToMessage: { _, _, _ in
|
||||
}, navigateToMessageStandalone: { _ in
|
||||
}, navigateToThreadMessage: { _, _, _ in
|
||||
}, tapMessage: nil, clickThroughMessage: {
|
||||
|
|
@ -98,7 +98,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
|||
}, requestMessageActionCallback: { _, _, _, _ in
|
||||
}, requestMessageActionUrlAuth: { _, _ in
|
||||
}, activateSwitchInline: { _, _, _ in
|
||||
}, openUrl: { _, _, _, _ in
|
||||
}, openUrl: { _, _, _, _, _ in
|
||||
}, shareCurrentLocation: {
|
||||
}, shareAccountContact: {
|
||||
}, sendBotCommand: { _, _ in
|
||||
|
|
|
|||
|
|
@ -367,7 +367,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
|||
}
|
||||
if let id = state.id as? PeerMessagesMediaPlaylistItemId, let playlistLocation = strongSelf.playlistLocation as? PeerMessagesPlaylistLocation, case let .messages(chatLocation, _, _) = playlistLocation {
|
||||
if type == .music {
|
||||
let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(location: .id(id.messageId), count: 60, highlight: true), id: 0), context: strongSelf.context, chatLocation: .peer(id: id.messageId.peerId), subject: nil, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), tagMask: MessageTags.music)
|
||||
let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(id.messageId), quote: nil), count: 60, highlight: true), id: 0), context: strongSelf.context, chatLocation: .peer(id: id.messageId.peerId), subject: nil, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), tagMask: MessageTags.music)
|
||||
|
||||
var cancelImpl: (() -> Void)?
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
|
|
|||
|
|
@ -2729,7 +2729,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}
|
||||
}
|
||||
})
|
||||
}, navigateToMessage: { fromId, id in
|
||||
}, navigateToMessage: { _, _, _ in
|
||||
}, navigateToMessageStandalone: { _ in
|
||||
}, navigateToThreadMessage: { _, _, _ in
|
||||
}, tapMessage: nil, clickThroughMessage: {
|
||||
|
|
@ -2766,7 +2766,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}, requestMessageActionCallback: { _, _, _, _ in
|
||||
}, requestMessageActionUrlAuth: { _, _ in
|
||||
}, activateSwitchInline: { _, _, _ in
|
||||
}, openUrl: { [weak self] url, concealed, external, _ in
|
||||
}, openUrl: { [weak self] url, concealed, external, _, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie
|
|||
adjustedUpdateItems.append(ChatHistoryViewTransitionUpdateEntry(index: adjustedIndex, previousIndex: adjustedPreviousIndex, entry: entry, directionHint: directionHint))
|
||||
}
|
||||
|
||||
var scrolledToIndex: MessageHistoryAnchorIndex?
|
||||
var scrolledToIndex: MessageHistoryScrollToSubject?
|
||||
var scrolledToSomeIndex = false
|
||||
|
||||
let curve: ListViewAnimationCurve = scrollAnimationCurve ?? .Default(duration: nil)
|
||||
|
|
@ -193,13 +193,14 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie
|
|||
index += 1
|
||||
}
|
||||
}
|
||||
case let .index(scrollIndex, position, directionHint, animated, highlight, displayLink):
|
||||
case let .index(scrollSubject, position, directionHint, animated, highlight, displayLink):
|
||||
let scrollIndex = scrollSubject
|
||||
if case .center = position, highlight {
|
||||
scrolledToIndex = scrollIndex
|
||||
scrolledToIndex = scrollSubject
|
||||
}
|
||||
var index = toView.filteredEntries.count - 1
|
||||
for entry in toView.filteredEntries {
|
||||
if scrollIndex.isLessOrEqual(to: entry.index) {
|
||||
if scrollIndex.index.isLessOrEqual(to: entry.index) {
|
||||
scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: curve, directionHint: directionHint, displayLink: displayLink)
|
||||
break
|
||||
}
|
||||
|
|
@ -209,7 +210,7 @@ func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toVie
|
|||
if scrollToItem == nil {
|
||||
var index = 0
|
||||
for entry in toView.filteredEntries.reversed() {
|
||||
if !scrollIndex.isLess(than: entry.index) {
|
||||
if !scrollIndex.index.isLess(than: entry.index) {
|
||||
scrolledToSomeIndex = true
|
||||
scrollToItem = ListViewScrollToItem(index: index, position: position, animated: animated, curve: curve, directionHint: directionHint)
|
||||
break
|
||||
|
|
|
|||
|
|
@ -1482,7 +1482,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in
|
||||
return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in
|
||||
}, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in
|
||||
}, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
|
||||
}, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _, _ in }, navigateToMessageStandalone: { _ in
|
||||
}, navigateToThreadMessage: { _, _, _ in
|
||||
}, tapMessage: { message in
|
||||
tapMessage?(message)
|
||||
|
|
@ -1490,7 +1490,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
clickThroughMessage?()
|
||||
}, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in
|
||||
return false
|
||||
}, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
}, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { _, _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
}, presentController: { _, _ in
|
||||
}, presentControllerInCurrent: { _, _ in
|
||||
}, navigationController: {
|
||||
|
|
|
|||
|
|
@ -210,7 +210,13 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
|
|||
nsString = text as NSString
|
||||
}
|
||||
string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Pre), value: nsString!.substring(with: range), range: range)
|
||||
case .BlockQuote, .Code:
|
||||
case .Code:
|
||||
string.addAttribute(NSAttributedString.Key.font, value: fixedFont, range: range)
|
||||
if nsString == nil {
|
||||
nsString = text as NSString
|
||||
}
|
||||
string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Pre), value: nsString!.substring(with: range), range: range)
|
||||
case .BlockQuote:
|
||||
if let fontAttribute = fontAttributes[range] {
|
||||
fontAttributes[range] = fontAttribute.union(.blockQuote)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -217,7 +217,9 @@ public func chatTranslationState(context: AccountContext, peerId: EnginePeer.Id)
|
|||
ranges.append(text.index(text.startIndex, offsetBy: entity.range.lowerBound) ..< text.index(text.startIndex, offsetBy: entity.range.upperBound))
|
||||
}
|
||||
for range in ranges {
|
||||
text.removeSubrange(range)
|
||||
if range.upperBound < text.endIndex {
|
||||
text.removeSubrange(range)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue