Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2022-05-31 04:44:51 +04:00
commit 6827584cd8
39 changed files with 500 additions and 248 deletions

View File

@ -2692,7 +2692,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
var apparentFrame = node.apparentFrame
apparentFrame.size.height = updatedApparentHeight
apply().1(ListViewItemApply(isOnScreen: visibleBounds.intersects(apparentFrame), timestamp: timestamp))
let applyContext = ListViewItemApply(isOnScreen: visibleBounds.intersects(apparentFrame), timestamp: timestamp)
apply().1(applyContext)
let invertOffsetDirection = applyContext.invertOffsetDirection
var offsetRanges = OffsetRanges()
@ -2714,7 +2716,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} else {
node.apparentHeight = previousApparentHeight
node.animateFrameTransition(0.0, previousApparentHeight)
node.addApparentHeightAnimation(updatedApparentHeight, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in
node.addApparentHeightAnimation(updatedApparentHeight, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, invertOffsetDirection: invertOffsetDirection, update: { [weak node] progress, currentValue in
if let node = node {
node.animateFrameTransition(progress, currentValue)
}
@ -4224,7 +4226,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
let itemNode = self.itemNodes[index]
let previousApparentHeight = itemNode.apparentHeight
if itemNode.animate(timestamp) {
var invertOffsetDirection = false
if itemNode.animate(timestamp: timestamp, invertOffsetDirection: &invertOffsetDirection) {
continueAnimations = true
}
let updatedApparentHeight = itemNode.apparentHeight
@ -4236,6 +4239,26 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
if itemNode.apparentFrame.maxY <= visualInsets.top {
offsetRanges.offset(IndexRange(first: 0, last: index), offset: -apparentHeightDelta)
} else if invertOffsetDirection {
if itemNode.apparentFrame.minY - apparentHeightDelta < visualInsets.top {
let overflowOffset = visualInsets.top - (itemNode.apparentFrame.minY - apparentHeightDelta)
let remainingOffset = apparentHeightDelta - overflowOffset
offsetRanges.offset(IndexRange(first: 0, last: index), offset: -remainingOffset)
var offsetDelta = overflowOffset
if offsetDelta < 0.0 {
let maxDelta = visualInsets.top - itemNode.apparentFrame.maxY
if maxDelta > offsetDelta {
let remainingOffset = maxDelta - offsetDelta
offsetRanges.offset(IndexRange(first: 0, last: index), offset: remainingOffset)
offsetDelta = maxDelta
}
}
offsetRanges.offset(IndexRange(first: index + 1, last: Int.max), offset: offsetDelta)
} else {
offsetRanges.offset(IndexRange(first: 0, last: index), offset: -apparentHeightDelta)
}
} else {
var offsetDelta = apparentHeightDelta
if offsetDelta < 0.0 {

View File

@ -132,15 +132,17 @@ public final class ListViewAnimation {
let to: Interpolatable
let duration: Double
let startTime: Double
let invertOffsetDirection: Bool
private let curve: (CGFloat) -> CGFloat
private let interpolator: (Interpolatable, Interpolatable, CGFloat) -> Interpolatable
private let update: (CGFloat, Interpolatable) -> Void
private let completed: (Bool) -> Void
public init<T: Interpolatable>(from: T, to: T, duration: Double, curve: @escaping (CGFloat) -> CGFloat, beginAt: Double, update: @escaping (CGFloat, T) -> Void, completed: @escaping (Bool) -> Void = { _ in }) {
public init<T: Interpolatable>(from: T, to: T, duration: Double, invertOffsetDirection: Bool = false, curve: @escaping (CGFloat) -> CGFloat, beginAt: Double, update: @escaping (CGFloat, T) -> Void, completed: @escaping (Bool) -> Void = { _ in }) {
self.from = from
self.to = to
self.duration = duration
self.invertOffsetDirection = invertOffsetDirection
self.curve = curve
self.startTime = beginAt
self.interpolator = T.interpolator()
@ -157,6 +159,7 @@ public final class ListViewAnimation {
self.curve = copying.curve
self.startTime = copying.startTime
self.interpolator = copying.interpolator
self.invertOffsetDirection = copying.invertOffsetDirection
self.update = { progress, value in
update(progress, value as! T)
}

View File

@ -50,14 +50,19 @@ public struct ListViewItemConfigureNodeFlags: OptionSet {
public static let preferSynchronousResourceLoading = ListViewItemConfigureNodeFlags(rawValue: 1 << 0)
}
public struct ListViewItemApply {
public final class ListViewItemApply {
public let isOnScreen: Bool
public let timestamp: Double?
public private(set) var invertOffsetDirection: Bool = false
public init(isOnScreen: Bool, timestamp: Double? = nil) {
self.isOnScreen = isOnScreen
self.timestamp = timestamp
}
public func setInvertOffsetDirection() {
self.invertOffsetDirection = true
}
}
public protocol ListViewItem {

View File

@ -334,7 +334,7 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
}
}
public func animate(_ timestamp: Double) -> Bool {
public func animate(timestamp: Double, invertOffsetDirection: inout Bool) -> Bool {
var continueAnimations = false
if let _ = self.spring {
@ -378,6 +378,10 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
let (_, animation) = self.animations[i]
animation.applyAt(timestamp)
if animation.invertOffsetDirection {
invertOffsetDirection = true
}
if animation.completeAt(timestamp) {
self.animations.remove(at: i)
animationCount -= 1
@ -521,9 +525,9 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
}
}
public func addApparentHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) {
public func addApparentHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, invertOffsetDirection: Bool = false, update: ((CGFloat, CGFloat) -> Void)? = nil) {
self.apparentHeightTransition = (self.apparentHeight, value)
let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: self.preferredAnimationCurve, beginAt: beginAt, update: { [weak self] progress, currentValue in
let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, invertOffsetDirection: invertOffsetDirection, curve: self.preferredAnimationCurve, beginAt: beginAt, update: { [weak self] progress, currentValue in
if let strongSelf = self {
strongSelf.apparentHeight = currentValue
if let update = update {

View File

@ -7,6 +7,7 @@ private var sharedRecognizers: [String: NSObject] = [:]
private struct TranscriptionResult {
var text: String
var confidence: Float
var isFinal: Bool
}
private func transcribeAudio(path: String, locale: String) -> Signal<TranscriptionResult?, NoError> {
@ -53,7 +54,7 @@ private func transcribeAudio(path: String, locale: String) -> Signal<Transcripti
let request = SFSpeechURLRecognitionRequest(url: URL(fileURLWithPath: tempFilePath))
request.requiresOnDeviceRecognition = speechRecognizer.supportsOnDeviceRecognition
request.shouldReportPartialResults = false
request.shouldReportPartialResults = true
let task = speechRecognizer.recognitionTask(with: request, resultHandler: { result, error in
if let result = result {
@ -62,8 +63,11 @@ private func transcribeAudio(path: String, locale: String) -> Signal<Transcripti
confidence += segment.confidence
}
confidence /= Float(result.bestTranscription.segments.count)
subscriber.putNext(TranscriptionResult(text: result.bestTranscription.formattedString, confidence: confidence))
subscriber.putCompletion()
subscriber.putNext(TranscriptionResult(text: result.bestTranscription.formattedString, confidence: confidence, isFinal: result.isFinal))
if result.isFinal {
subscriber.putCompletion()
}
} else {
print("transcribeAudio: locale: \(locale), error: \(String(describing: error))")
@ -91,7 +95,12 @@ private func transcribeAudio(path: String, locale: String) -> Signal<Transcripti
|> runOn(.mainQueue())
}
public func transcribeAudio(path: String, appLocale: String) -> Signal<String?, NoError> {
public struct LocallyTranscribedAudio {
public var text: String
public var isFinal: Bool
}
public func transcribeAudio(path: String, appLocale: String) -> Signal<LocallyTranscribedAudio?, NoError> {
var signals: [Signal<TranscriptionResult?, NoError>] = []
var locales: [String] = []
if !locales.contains(Locale.current.identifier) {
@ -113,10 +122,12 @@ public func transcribeAudio(path: String, appLocale: String) -> Signal<String?,
}
return resultSignal
|> map { results -> String? in
|> map { results -> LocallyTranscribedAudio? in
let sortedResults = results.compactMap({ $0 }).sorted(by: { lhs, rhs in
return lhs.confidence > rhs.confidence
})
return sortedResults.first?.text
return sortedResults.first.flatMap { result -> LocallyTranscribedAudio in
return LocallyTranscribedAudio(text: result.text, isFinal: result.isFinal)
}
}
}

View File

@ -885,9 +885,10 @@ struct ctr_state {
int greaseCount = 8;
NSMutableData *greaseData = [[NSMutableData alloc] initWithLength:greaseCount];
uint8_t *greaseBytes = (uint8_t *)greaseData.mutableBytes;
int result;
result = SecRandomCopyBytes(nil, greaseData.length, greaseData.mutableBytes);
assert(result == errSecSuccess);
int result = SecRandomCopyBytes(nil, greaseData.length, greaseData.mutableBytes);
if (result != errSecSuccess) {
assert(false);
}
for (int i = 0; i < greaseData.length; i++) {
uint8_t c = greaseBytes[i];

View File

@ -1474,6 +1474,24 @@ final class MessageHistoryTable: Table {
}
}
var previousAttributes: [MessageAttribute] = []
let attributesData = previousMessage.attributesData.sharedBufferNoCopy()
if attributesData.length > 4 {
var attributeCount: Int32 = 0
attributesData.read(&attributeCount, offset: 0, length: 4)
for _ in 0 ..< attributeCount {
var attributeLength: Int32 = 0
attributesData.read(&attributeLength, offset: 0, length: 4)
if let attribute = PostboxDecoder(buffer: MemoryBuffer(memory: attributesData.memory + attributesData.offset, capacity: Int(attributeLength), length: Int(attributeLength), freeWhenDone: false)).decodeRootObject() as? MessageAttribute {
previousAttributes.append(attribute)
}
attributesData.skip(Int(attributeLength))
}
}
var updatedAttributes = message.attributes
self.seedConfiguration.mergeMessageAttributes(previousAttributes, &updatedAttributes)
self.valueBox.remove(self.table, key: self.key(index), secure: true)
let updatedIndex = message.index
@ -1534,14 +1552,14 @@ final class MessageHistoryTable: Table {
for tag in previousTimestampBasedAttibutes.keys {
self.timeBasedAttributesTable.remove(tag: tag, id: previousMessage.id, operations: &timestampBasedMessageAttributesOperations)
}
for attribute in message.attributes {
for attribute in updatedAttributes {
if let (tag, timestamp) = attribute.automaticTimestampBasedAttribute {
self.timeBasedAttributesTable.set(tag: tag, id: message.id, timestamp: timestamp, operations: &timestampBasedMessageAttributesOperations)
}
}
} else {
var updatedTimestampBasedAttibuteTags: [UInt16] = []
for attribute in message.attributes {
for attribute in updatedAttributes {
if let (tag, timestamp) = attribute.automaticTimestampBasedAttribute {
updatedTimestampBasedAttibuteTags.append(tag)
if previousTimestampBasedAttibutes[tag] != timestamp {
@ -1778,9 +1796,9 @@ final class MessageHistoryTable: Table {
let attributesBuffer = WriteBuffer()
var attributeCount: Int32 = Int32(message.attributes.count)
var attributeCount: Int32 = Int32(updatedAttributes.count)
attributesBuffer.write(&attributeCount, offset: 0, length: 4)
for attribute in message.attributes {
for attribute in updatedAttributes {
sharedEncoder.reset()
sharedEncoder.encodeRootObject(attribute)
let attributeBuffer = sharedEncoder.memoryBuffer()

View File

@ -71,6 +71,7 @@ public final class SeedConfiguration {
public let chatMessagesNamespaces: Set<MessageId.Namespace>
public let getGlobalNotificationSettings: (Transaction) -> PostboxGlobalNotificationSettings?
public let defaultGlobalNotificationSettings: PostboxGlobalNotificationSettings
public let mergeMessageAttributes: ([MessageAttribute], inout [MessageAttribute]) -> Void
public init(
globalMessageIdsPeerIdNamespaces: Set<GlobalMessageIdsNamespace>,
@ -91,7 +92,8 @@ public final class SeedConfiguration {
defaultMessageNamespaceReadStates: [MessageId.Namespace: PeerReadState],
chatMessagesNamespaces: Set<MessageId.Namespace>,
getGlobalNotificationSettings: @escaping (Transaction) -> PostboxGlobalNotificationSettings?,
defaultGlobalNotificationSettings: PostboxGlobalNotificationSettings
defaultGlobalNotificationSettings: PostboxGlobalNotificationSettings,
mergeMessageAttributes: @escaping ([MessageAttribute], inout [MessageAttribute]) -> Void
) {
self.globalMessageIdsPeerIdNamespaces = globalMessageIdsPeerIdNamespaces
self.initializeChatListWithHole = initializeChatListWithHole
@ -108,5 +110,6 @@ public final class SeedConfiguration {
self.chatMessagesNamespaces = chatMessagesNamespaces
self.getGlobalNotificationSettings = getGlobalNotificationSettings
self.defaultGlobalNotificationSettings = defaultGlobalNotificationSettings
self.mergeMessageAttributes = mergeMessageAttributes
}
}

View File

@ -113,6 +113,7 @@ enum AccountStateMutationOperation {
case UpdateGroupCall(peerId: PeerId, call: Api.GroupCall)
case UpdateAutoremoveTimeout(peer: Api.Peer, value: CachedPeerAutoremoveTimeout.Value?)
case UpdateAttachMenuBots
case UpdateAudioTranscription(id: Int64, isPending: Bool, text: String)
}
struct HoleFromPreviousState {
@ -508,13 +509,17 @@ struct AccountMutableState {
self.addOperation(.UpdateAttachMenuBots)
}
mutating func updateAudioTranscription(id: Int64, isPending: Bool, text: String) {
self.addOperation(.UpdateAudioTranscription(id: id, isPending: isPending, text: text))
}
mutating func addDismissedWebView(queryId: Int64) {
self.addOperation(.UpdateAttachMenuBots)
}
mutating func addOperation(_ operation: AccountStateMutationOperation) {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription:
break
case let .AddMessages(messages, location):
for message in messages {

View File

@ -1101,8 +1101,9 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
updatedState.updateMedia(webpage.webpageId, media: webpage)
}
}
/*case let .updateTranscribeAudio(flags, transcriptionId, text):
break*/
case let .updateTranscribeAudio(flags, transcriptionId, text):
let isPending = (flags & (1 << 0)) != 0
updatedState.updateAudioTranscription(id: transcriptionId, isPending: isPending, text: text)
case let .updateNotifySettings(apiPeer, apiNotificationSettings):
switch apiPeer {
case let .notifyPeer(peer):
@ -2321,7 +2322,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
var currentAddScheduledMessages: OptimizeAddMessagesState?
for operation in operations {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription:
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
}
@ -2402,7 +2403,8 @@ func replayFinalState(
auxiliaryMethods: AccountAuxiliaryMethods,
finalState: AccountFinalState,
removePossiblyDeliveredMessagesUniqueIds: [Int64: PeerId],
ignoreDate: Bool
ignoreDate: Bool,
audioTranscriptionManager: Atomic<AudioTranscriptionManager>?
) -> AccountReplayedFinalState? {
let verified = verifyTransaction(transaction, finalState: finalState.state)
if !verified {
@ -3342,6 +3344,48 @@ func replayFinalState(
})
case .UpdateAttachMenuBots:
syncAttachMenuBots = true
case let .UpdateAudioTranscription(id, isPending, text):
if let audioTranscriptionManager = audioTranscriptionManager {
if let messageId = audioTranscriptionManager.with({ audioTranscriptionManager in
return audioTranscriptionManager.getPendingMapping(transcriptionId: id)
}) {
transaction.updateMessage(messageId, update: { currentMessage in
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
}
var attributes = currentMessage.attributes
var found = false
loop: for j in 0 ..< attributes.count {
if let attribute = attributes[j] as? AudioTranscriptionMessageAttribute {
attributes[j] = AudioTranscriptionMessageAttribute(id: id, text: text, isPending: isPending, didRate: attribute.didRate)
found = true
break loop
}
}
if !found {
attributes.append(AudioTranscriptionMessageAttribute(id: id, text: text, isPending: isPending, didRate: false))
}
return .update(StoreMessage(
id: currentMessage.id,
globallyUniqueId: currentMessage.globallyUniqueId,
groupingKey: currentMessage.groupingKey,
threadId: currentMessage.threadId,
timestamp: currentMessage.timestamp,
flags: StoreMessageFlags(currentMessage.flags),
tags: currentMessage.tags,
globalTags: currentMessage.globalTags,
localTags: currentMessage.localTags,
forwardInfo: storeForwardInfo,
authorId: currentMessage.author?.id,
text: currentMessage.text,
attributes: attributes,
media: currentMessage.media
))
})
}
}
}
}

View File

@ -64,6 +64,8 @@ public final class AccountStateManager {
let auxiliaryMethods: AccountAuxiliaryMethods
var transformOutgoingMessageMedia: TransformOutgoingMessageMedia?
let audioTranscriptionManager = Atomic<AudioTranscriptionManager>(value: AudioTranscriptionManager())
private var updateService: UpdateMessageService?
private let updateServiceDisposable = MetaDisposable()
@ -425,6 +427,7 @@ public final class AccountStateManager {
let mediaBox = postbox.mediaBox
let accountPeerId = self.accountPeerId
let auxiliaryMethods = self.auxiliaryMethods
let audioTranscriptionManager = self.audioTranscriptionManager
let signal = postbox.stateView()
|> mapToSignal { view -> Signal<AuthorizedAccountState, NoError> in
if let state = view.state as? AuthorizedAccountState {
@ -476,7 +479,7 @@ public final class AccountStateManager {
let removePossiblyDeliveredMessagesUniqueIds = self?.removePossiblyDeliveredMessagesUniqueIds ?? Dictionary()
return postbox.transaction { transaction -> (difference: Api.updates.Difference?, finalStatte: AccountReplayedFinalState?, skipBecauseOfError: Bool) in
let startTime = CFAbsoluteTimeGetCurrent()
let replayedState = replayFinalState(accountManager: accountManager, postbox: postbox, accountPeerId: accountPeerId, mediaBox: mediaBox, encryptionProvider: network.encryptionProvider, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState, removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds, ignoreDate: false)
let replayedState = replayFinalState(accountManager: accountManager, postbox: postbox, accountPeerId: accountPeerId, mediaBox: mediaBox, encryptionProvider: network.encryptionProvider, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState, removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds, ignoreDate: false, audioTranscriptionManager: audioTranscriptionManager)
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime
if deltaTime > 1.0 {
Logger.shared.log("State", "replayFinalState took \(deltaTime)s")
@ -575,6 +578,7 @@ public final class AccountStateManager {
let auxiliaryMethods = self.auxiliaryMethods
let accountPeerId = self.accountPeerId
let mediaBox = postbox.mediaBox
let audioTranscriptionManager = self.audioTranscriptionManager
let queue = self.queue
let signal = initialStateWithUpdateGroups(postbox: postbox, groups: groups)
|> mapToSignal { [weak self] state -> Signal<(AccountReplayedFinalState?, AccountFinalState), NoError> in
@ -594,7 +598,7 @@ public final class AccountStateManager {
return nil
} else {
let startTime = CFAbsoluteTimeGetCurrent()
let result = replayFinalState(accountManager: accountManager, postbox: postbox, accountPeerId: accountPeerId, mediaBox: mediaBox, encryptionProvider: network.encryptionProvider, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState, removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds, ignoreDate: false)
let result = replayFinalState(accountManager: accountManager, postbox: postbox, accountPeerId: accountPeerId, mediaBox: mediaBox, encryptionProvider: network.encryptionProvider, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState, removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds, ignoreDate: false, audioTranscriptionManager: audioTranscriptionManager)
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime
if deltaTime > 1.0 {
Logger.shared.log("State", "replayFinalState took \(deltaTime)s")
@ -827,10 +831,11 @@ public final class AccountStateManager {
let mediaBox = self.postbox.mediaBox
let network = self.network
let auxiliaryMethods = self.auxiliaryMethods
let audioTranscriptionManager = self.audioTranscriptionManager
let removePossiblyDeliveredMessagesUniqueIds = self.removePossiblyDeliveredMessagesUniqueIds
let signal = self.postbox.transaction { transaction -> AccountReplayedFinalState? in
let startTime = CFAbsoluteTimeGetCurrent()
let result = replayFinalState(accountManager: accountManager, postbox: postbox, accountPeerId: accountPeerId, mediaBox: mediaBox, encryptionProvider: network.encryptionProvider, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState, removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds, ignoreDate: false)
let result = replayFinalState(accountManager: accountManager, postbox: postbox, accountPeerId: accountPeerId, mediaBox: mediaBox, encryptionProvider: network.encryptionProvider, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState, removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds, ignoreDate: false, audioTranscriptionManager: audioTranscriptionManager)
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime
if deltaTime > 1.0 {
Logger.shared.log("State", "replayFinalState took \(deltaTime)s")
@ -872,10 +877,11 @@ public final class AccountStateManager {
let mediaBox = self.postbox.mediaBox
let network = self.network
let auxiliaryMethods = self.auxiliaryMethods
let audioTranscriptionManager = self.audioTranscriptionManager
let removePossiblyDeliveredMessagesUniqueIds = self.removePossiblyDeliveredMessagesUniqueIds
let signal = self.postbox.transaction { transaction -> AccountReplayedFinalState? in
let startTime = CFAbsoluteTimeGetCurrent()
let result = replayFinalState(accountManager: accountManager, postbox: postbox, accountPeerId: accountPeerId, mediaBox: mediaBox, encryptionProvider: network.encryptionProvider, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState, removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds, ignoreDate: false)
let result = replayFinalState(accountManager: accountManager, postbox: postbox, accountPeerId: accountPeerId, mediaBox: mediaBox, encryptionProvider: network.encryptionProvider, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState, removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds, ignoreDate: false, audioTranscriptionManager: audioTranscriptionManager)
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime
if deltaTime > 1.0 {
Logger.shared.log("State", "replayFinalState took \(deltaTime)s")
@ -897,6 +903,7 @@ public final class AccountStateManager {
let mediaBox = postbox.mediaBox
let accountPeerId = self.accountPeerId
let auxiliaryMethods = self.auxiliaryMethods
let audioTranscriptionManager = self.audioTranscriptionManager
let signal = postbox.stateView()
|> mapToSignal { view -> Signal<AuthorizedAccountState, NoError> in
@ -959,7 +966,8 @@ public final class AccountStateManager {
auxiliaryMethods: auxiliaryMethods,
finalState: finalState,
removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds,
ignoreDate: true
ignoreDate: true,
audioTranscriptionManager: audioTranscriptionManager
)
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime
if deltaTime > 1.0 {

View File

@ -4,27 +4,31 @@ public class AudioTranscriptionMessageAttribute: MessageAttribute, Equatable {
public let id: Int64
public let text: String
public let isPending: Bool
public let didRate: Bool
public var associatedPeerIds: [PeerId] {
return []
}
public init(id: Int64, text: String, isPending: Bool) {
public init(id: Int64, text: String, isPending: Bool, didRate: Bool) {
self.id = id
self.text = text
self.isPending = isPending
self.didRate = didRate
}
required public init(decoder: PostboxDecoder) {
self.id = decoder.decodeInt64ForKey("id", orElse: 0)
self.text = decoder.decodeStringForKey("text", orElse: "")
self.isPending = decoder.decodeBoolForKey("isPending", orElse: false)
self.didRate = decoder.decodeBoolForKey("didRate", orElse: false)
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt64(self.id, forKey: "id")
encoder.encodeString(self.text, forKey: "text")
encoder.encodeBool(self.isPending, forKey: "isPending")
encoder.encodeBool(self.didRate, forKey: "didRate")
}
public static func ==(lhs: AudioTranscriptionMessageAttribute, rhs: AudioTranscriptionMessageAttribute) -> Bool {
@ -37,6 +41,17 @@ public class AudioTranscriptionMessageAttribute: MessageAttribute, Equatable {
if lhs.isPending != rhs.isPending {
return false
}
if lhs.didRate != rhs.didRate {
return false
}
return true
}
func merge(withPrevious other: AudioTranscriptionMessageAttribute) -> AudioTranscriptionMessageAttribute {
return AudioTranscriptionMessageAttribute(id: self.id, text: self.text, isPending: self.isPending, didRate: self.didRate || other.didRate)
}
func withDidRate() -> AudioTranscriptionMessageAttribute {
return AudioTranscriptionMessageAttribute(id: self.id, text: self.text, isPending: self.isPending, didRate: true)
}
}

View File

@ -89,7 +89,33 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = {
},
defaultGlobalNotificationSettings: PostboxGlobalNotificationSettings(defaultIncludePeer: { peer in
return GlobalNotificationSettings.defaultSettings.defaultIncludePeer(peer: peer)
})
}),
mergeMessageAttributes: { previous, updated in
if previous.isEmpty {
return
}
var audioTranscription: AudioTranscriptionMessageAttribute?
for attribute in previous {
if let attribute = attribute as? AudioTranscriptionMessageAttribute {
audioTranscription = attribute
break
}
}
if let audioTranscription = audioTranscription {
var found = false
for i in 0 ..< updated.count {
if let attribute = updated[i] as? AudioTranscriptionMessageAttribute {
updated[i] = attribute.merge(withPrevious: audioTranscription)
found = true
break
}
}
if !found {
updated.append(audioTranscription)
}
}
}
)
}()

View File

@ -329,7 +329,21 @@ public extension TelegramEngine {
}
public func transcribeAudio(messageId: MessageId) -> Signal<EngineAudioTranscriptionResult, NoError> {
return _internal_transcribeAudio(postbox: self.account.postbox, network: self.account.network, messageId: messageId)
return _internal_transcribeAudio(postbox: self.account.postbox, network: self.account.network, audioTranscriptionManager: self.account.stateManager.audioTranscriptionManager, messageId: messageId)
}
public func storeLocallyTranscribedAudio(messageId: MessageId, text: String, isFinal: Bool) -> Signal<Never, NoError> {
return self.account.postbox.transaction { transaction -> Void in
transaction.updateMessage(messageId, update: { currentMessage in
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
var attributes = currentMessage.attributes.filter { !($0 is AudioTranscriptionMessageAttribute) }
attributes.append(AudioTranscriptionMessageAttribute(id: 0, text: text, isPending: !isFinal, didRate: false))
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
}
|> ignoreValues
}
public func rateAudioTranscription(messageId: MessageId, id: Int64, isGood: Bool) -> Signal<Never, NoError> {

View File

@ -30,21 +30,26 @@ func _internal_translate(network: Network, text: String, fromLang: String?, toLa
}
public enum EngineAudioTranscriptionResult {
public struct Success {
public var id: Int64
public var text: String
public init(id: Int64, text: String) {
self.id = id
self.text = text
}
}
case success(Success)
case success
case error
}
func _internal_transcribeAudio(postbox: Postbox, network: Network, messageId: MessageId) -> Signal<EngineAudioTranscriptionResult, NoError> {
class AudioTranscriptionManager {
private var pendingMapping: [Int64: MessageId] = [:]
init() {
}
func addPendingMapping(transcriptionId: Int64, messageId: MessageId) {
self.pendingMapping[transcriptionId] = messageId
}
func getPendingMapping(transcriptionId: Int64) -> MessageId? {
return self.pendingMapping[transcriptionId]
}
}
func _internal_transcribeAudio(postbox: Postbox, network: Network, audioTranscriptionManager: Atomic<AudioTranscriptionManager>, messageId: MessageId) -> Signal<EngineAudioTranscriptionResult, NoError> {
return postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
}
@ -58,25 +63,38 @@ func _internal_transcribeAudio(postbox: Postbox, network: Network, messageId: Me
return .single(nil)
}
|> mapToSignal { result -> Signal<EngineAudioTranscriptionResult, NoError> in
guard let result = result else {
return .single(.error)
}
return postbox.transaction { transaction -> EngineAudioTranscriptionResult in
switch result {
case let .transcribedAudio(flags, transcriptionId, text):
transaction.updateMessage(messageId, update: { currentMessage in
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
var attributes = currentMessage.attributes.filter { !($0 is AudioTranscriptionMessageAttribute) }
let updatedAttribute: AudioTranscriptionMessageAttribute
if let result = result {
switch result {
case let .transcribedAudio(flags, transcriptionId, text):
let isPending = (flags & (1 << 0)) != 0
attributes.append(AudioTranscriptionMessageAttribute(id: transcriptionId, text: text, isPending: isPending))
if isPending {
audioTranscriptionManager.with { audioTranscriptionManager in
audioTranscriptionManager.addPendingMapping(transcriptionId: transcriptionId, messageId: messageId)
}
}
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
updatedAttribute = AudioTranscriptionMessageAttribute(id: transcriptionId, text: text, isPending: isPending, didRate: false)
}
} else {
updatedAttribute = AudioTranscriptionMessageAttribute(id: 0, text: "", isPending: false, didRate: false)
}
return .success(EngineAudioTranscriptionResult.Success(id: transcriptionId, text: text))
transaction.updateMessage(messageId, update: { currentMessage in
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
var attributes = currentMessage.attributes.filter { !($0 is AudioTranscriptionMessageAttribute) }
attributes.append(updatedAttribute)
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
if let _ = result {
return .success
} else {
return .error
}
}
}
@ -85,6 +103,35 @@ func _internal_transcribeAudio(postbox: Postbox, network: Network, messageId: Me
func _internal_rateAudioTranscription(postbox: Postbox, network: Network, messageId: MessageId, id: Int64, isGood: Bool) -> Signal<Never, NoError> {
return postbox.transaction { transaction -> Api.InputPeer? in
transaction.updateMessage(messageId, update: { currentMessage in
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
}
var attributes = currentMessage.attributes
for i in 0 ..< attributes.count {
if let attribute = attributes[i] as? AudioTranscriptionMessageAttribute {
attributes[i] = attribute.withDidRate()
}
}
return .update(StoreMessage(
id: currentMessage.id,
globallyUniqueId: currentMessage.globallyUniqueId,
groupingKey: currentMessage.groupingKey,
threadId: currentMessage.threadId,
timestamp: currentMessage.timestamp,
flags: StoreMessageFlags(currentMessage.flags),
tags: currentMessage.tags,
globalTags: currentMessage.globalTags,
localTags: currentMessage.localTags,
forwardInfo: storeForwardInfo,
authorId: currentMessage.author?.id,
text: currentMessage.text,
attributes: attributes,
media: currentMessage.media
))
})
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Never, NoError> in

View File

@ -514,7 +514,7 @@ public final class AudioWaveformComponent: Component {
let colorMixFraction: CGFloat
if startFraction < playbackProgress {
colorMixFraction = max(0.0, min(1.0, (playbackProgress - startFraction) / (playbackProgress - nextStartFraction)))
colorMixFraction = max(0.0, min(1.0, (playbackProgress - startFraction) / (nextStartFraction - startFraction)))
} else {
colorMixFraction = 0.0
}

View File

@ -696,15 +696,17 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
var audioTranscription: AudioTranscriptionMessageAttribute?
var didRateAudioTranscription = false
for attribute in message.attributes {
if let attribute = attribute as? AudioTranscriptionMessageAttribute {
audioTranscription = attribute
didRateAudioTranscription = attribute.didRate
break
}
}
var hasRateTranscription = false
if hasExpandedAudioTranscription, let audioTranscription = audioTranscription {
if hasExpandedAudioTranscription, let audioTranscription = audioTranscription, !didRateAudioTranscription {
hasRateTranscription = true
actions.insert(.custom(ChatRateTranscriptionContextItem(context: context, message: message, action: { [weak context] value in
guard let context = context else {

View File

@ -129,7 +129,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
return mediaHidden
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
let cachedMaskBackgroundImage = self.cachedMaskBackgroundImage
@ -195,7 +195,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
}
return (backgroundSize.width, { boundingWidth in
return (backgroundSize, { [weak self] animation, synchronousLoads in
return (backgroundSize, { [weak self] animation, synchronousLoads, _ in
if let strongSelf = self {
strongSelf.item = item

View File

@ -270,7 +270,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
self.addSubnode(self.statusNode)
}
func asyncLayout() -> (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ chatLocation: ChatLocation, _ title: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ constrainedSize: CGSize) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
func asyncLayout() -> (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ chatLocation: ChatLocation, _ title: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ constrainedSize: CGSize) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let textAsyncLayout = TextNode.asyncLayout(self.textNode)
let currentImage = self.media as? TelegramMediaImage
let imageLayout = self.inlineImageNode.asyncLayout()
@ -361,7 +361,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
var textCutout = TextNodeCutout()
var initialWidth: CGFloat = CGFloat.greatestFiniteMagnitude
var refineContentImageLayout: ((CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> ChatMessageInteractiveMediaNode)))?
var refineContentFileLayout: ((CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation) -> ChatMessageInteractiveFileNode)))?
var refineContentFileLayout: ((CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> ChatMessageInteractiveFileNode)))?
var contentInstantVideoSizeAndApply: (ChatMessageInstantVideoItemLayoutResult, (ChatMessageInstantVideoItemLayoutData, ListViewItemUpdateAnimation) -> ChatMessageInteractiveInstantVideoNode)?
@ -703,7 +703,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
boundingSize.width = max(boundingSize.width, refinedWidth)
}
var finalizeContentFileLayout: ((CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation) -> ChatMessageInteractiveFileNode))?
var finalizeContentFileLayout: ((CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> ChatMessageInteractiveFileNode))?
if let refineContentFileLayout = refineContentFileLayout {
let (refinedWidth, finalizeFileLayout) = refineContentFileLayout(textConstrainedSize)
finalizeContentFileLayout = finalizeFileLayout
@ -784,7 +784,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
adjustedLineHeight += imageHeightAddition + 4.0
}
var contentFileSizeAndApply: (CGSize, (Bool, ListViewItemUpdateAnimation) -> ChatMessageInteractiveFileNode)?
var contentFileSizeAndApply: (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> ChatMessageInteractiveFileNode)?
if let finalizeContentFileLayout = finalizeContentFileLayout {
let (size, apply) = finalizeContentFileLayout(boundingWidth - insets.left - insets.right)
contentFileSizeAndApply = (size, apply)
@ -829,7 +829,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
adjustedBoundingSize.width = max(boundingWidth, adjustedBoundingSize.width)
return (adjustedBoundingSize, { [weak self] animation, synchronousLoads in
return (adjustedBoundingSize, { [weak self] animation, synchronousLoads, applyInfo in
if let strongSelf = self {
strongSelf.context = context
strongSelf.message = message
@ -922,7 +922,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
if let (contentFileSize, contentFileApply) = contentFileSizeAndApply {
contentMediaHeight = contentFileSize.height
let contentFileNode = contentFileApply(synchronousLoads, animation)
let contentFileNode = contentFileApply(synchronousLoads, animation, applyInfo)
if strongSelf.contentFileNode !== contentFileNode {
strongSelf.contentFileNode = contentFileNode
strongSelf.addSubnode(contentFileNode)

View File

@ -144,7 +144,7 @@ class ChatMessageBubbleContentNode: ASDisplayNode {
super.init()
}
func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
preconditionFailure()
}

View File

@ -986,7 +986,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
override func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
var currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))] = []
var currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))] = []
for contentNode in self.contentNodes {
if let message = contentNode.item?.message {
currentContentClassesPropertiesAndLayouts.append((message, type(of: contentNode) as AnyClass, contentNode.supportsMosaic, contentNode.asyncLayoutContent()))
@ -1033,7 +1033,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
private static func beginLayout(selfReference: Weak<ChatMessageBubbleItemNode>, _ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool,
currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))],
currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))],
authorNameLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
adminBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
@ -1277,7 +1277,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
let maximumContentWidth = floor(tmpWidth - layoutConstants.bubble.edgeInset - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - layoutConstants.bubble.contentInsets.right - avatarInset)
var contentPropertiesAndPrepareLayouts: [(Message, Bool, ChatMessageEntryAttributes, BubbleItemAttributes, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))] = []
var contentPropertiesAndPrepareLayouts: [(Message, Bool, ChatMessageEntryAttributes, BubbleItemAttributes, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))] = []
var addedContentNodes: [(Message, Bool, ChatMessageBubbleContentNode)]?
let (contentNodeMessagesAndClasses, needSeparateContainers, needReactions) = contentNodeMessagesAndClassesForItem(item)
@ -1286,7 +1286,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
var found = false
for currentNodeItemValue in currentContentClassesPropertiesAndLayouts {
let currentNodeItem = currentNodeItemValue as (message: Message, type: AnyClass, supportsMosaic: Bool, currentLayout: (ChatMessageBubbleContentItem, ChatMessageItemLayoutConstants, ChatMessageBubblePreparePosition, Bool?, CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))
let currentNodeItem = currentNodeItemValue as (message: Message, type: AnyClass, supportsMosaic: Bool, currentLayout: (ChatMessageBubbleContentItem, ChatMessageItemLayoutConstants, ChatMessageBubblePreparePosition, Bool?, CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))
if currentNodeItem.type == contentNodeItem.type && currentNodeItem.message.stableId == contentNodeItem.message.stableId {
contentPropertiesAndPrepareLayouts.append((contentNodeItem.message, currentNodeItem.supportsMosaic, contentNodeItem.attributes, contentNodeItem.bubbleAttributes, currentNodeItem.currentLayout))
@ -1357,7 +1357,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
inlineBotNameString = nil
}
var contentPropertiesAndLayouts: [(CGSize?, ChatMessageBubbleContentProperties, ChatMessageBubblePreparePosition, BubbleItemAttributes, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void)), UInt32?, Bool?)] = []
var contentPropertiesAndLayouts: [(CGSize?, ChatMessageBubbleContentProperties, ChatMessageBubblePreparePosition, BubbleItemAttributes, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void)), UInt32?, Bool?)] = []
var backgroundHiding: ChatMessageBubbleContentBackgroundHiding?
var hasSolidWallpaper = false
@ -1845,7 +1845,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
}
var contentNodePropertiesAndFinalize: [(ChatMessageBubbleContentProperties, ChatMessageBubbleContentPosition?, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void), UInt32?, Bool?)] = []
var contentNodePropertiesAndFinalize: [(ChatMessageBubbleContentProperties, ChatMessageBubbleContentPosition?, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void), UInt32?, Bool?)] = []
var maxContentWidth: CGFloat = headerSize.width
@ -2031,7 +2031,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
var contentSize = CGSize(width: maxContentWidth, height: 0.0)
var contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, Bool, (ListViewItemUpdateAnimation, Bool) -> Void)] = []
var contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, Bool, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void)] = []
var contentContainerNodeFrames: [(UInt32, CGRect, Bool?, CGFloat)] = []
var currentContainerGroupId: UInt32?
var currentItemSelection: Bool?
@ -2245,7 +2245,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
})
}
private static func applyLayout(selfReference: Weak<ChatMessageBubbleItemNode>, _ animation: ListViewItemUpdateAnimation, _ synchronousLoads: Bool,
private static func applyLayout(selfReference: Weak<ChatMessageBubbleItemNode>,
_ animation: ListViewItemUpdateAnimation,
_ synchronousLoads: Bool,
params: ListViewItemLayoutParams,
applyInfo: ListViewItemApply,
layout: ListViewItemNodeLayout,
@ -2278,7 +2280,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
removedContentNodeIndices: [Int]?,
addedContentNodes: [(Message, Bool, ChatMessageBubbleContentNode)]?,
contentNodeMessagesAndClasses: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)],
contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, Bool, (ListViewItemUpdateAnimation, Bool) -> Void)],
contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, Bool, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void)],
contentContainerNodeFrames: [(UInt32, CGRect, Bool?, CGFloat)],
mosaicStatusOrigin: CGPoint?,
mosaicStatusSizeAndApply: (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode)?,
@ -2730,7 +2732,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
var contentNodeIndex = 0
for (relativeFrame, _, useContentOrigin, apply) in contentNodeFramesPropertiesAndApply {
apply(animation, synchronousLoads)
apply(animation, synchronousLoads, applyInfo)
if contentNodeIndex >= strongSelf.contentNodes.count {
break
@ -3495,6 +3497,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
}
for contentNode in self.contentNodes {
if let result = contentNode.hitTest(self.view.convert(point, to: contentNode.view), with: event) {
return result
}
}
return super.hitTest(point, with: event)
}

View File

@ -62,7 +62,7 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
fatalError("init(coder:) has not been implemented")
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
@ -198,7 +198,7 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
boundingSize.width += 54.0
return (boundingSize.width, { boundingWidth in
return (boundingSize, { [weak self] animation, _ in
return (boundingSize, { [weak self] animation, _, _ in
if let strongSelf = self {
strongSelf.item = item

View File

@ -96,7 +96,7 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode {
}
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let makeCountLayout = self.countNode.asyncLayout()
let makeAlternativeCountLayout = self.alternativeCountNode.asyncLayout()
@ -249,7 +249,7 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode {
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
boundingSize.height = 40.0 + topOffset
return (boundingSize, { [weak self] animation, synchronousLoad in
return (boundingSize, { [weak self] animation, synchronousLoad, _ in
if let strongSelf = self {
strongSelf.item = item

View File

@ -75,7 +75,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
self.view.addGestureRecognizer(tapRecognizer)
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let statusLayout = self.dateAndStatusNode.asyncLayout()
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeTextLayout = TextNode.asyncLayout(self.textNode)
@ -296,7 +296,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
}
}
return (layoutSize, { [weak self] animation, synchronousLoads in
return (layoutSize, { [weak self] animation, synchronousLoads, _ in
if let strongSelf = self {
strongSelf.item = item
strongSelf.contact = selectedContact

View File

@ -21,7 +21,7 @@ final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubble
fatalError("init(coder:) has not been implemented")
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let contentNodeLayout = self.contentNode.asyncLayout()
return { item, layoutConstants, preparePosition, _, constrainedSize in
@ -53,11 +53,11 @@ final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubble
return (refinedWidth, { boundingWidth in
let (size, apply) = finalizeLayout(boundingWidth)
return (size, { [weak self] animation, synchronousLoads in
return (size, { [weak self] animation, synchronousLoads, applyInfo in
if let strongSelf = self {
strongSelf.item = item
apply(animation, synchronousLoads)
apply(animation, synchronousLoads, applyInfo)
strongSelf.contentNode.frame = CGRect(origin: CGPoint(), size: size)
}

View File

@ -21,7 +21,7 @@ final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContent
fatalError("init(coder:) has not been implemented")
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let contentNodeLayout = self.contentNode.asyncLayout()
return { item, layoutConstants, preparePosition, _, constrainedSize in
@ -48,11 +48,11 @@ final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContent
return (refinedWidth, { boundingWidth in
let (size, apply) = finalizeLayout(boundingWidth)
return (size, { [weak self] animation, synchronousLoads in
return (size, { [weak self] animation, synchronousLoads, applyInfo in
if let strongSelf = self {
strongSelf.item = item
apply(animation, synchronousLoads)
apply(animation, synchronousLoads, applyInfo)
strongSelf.contentNode.frame = CGRect(origin: CGPoint(), size: size)
}

View File

@ -21,7 +21,7 @@ final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleCont
fatalError("init(coder:) has not been implemented")
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let contentNodeLayout = self.contentNode.asyncLayout()
return { item, layoutConstants, preparePosition, _, constrainedSize in
@ -53,11 +53,11 @@ final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleCont
return (refinedWidth, { boundingWidth in
let (size, apply) = finalizeLayout(boundingWidth)
return (size, { [weak self] animation, synchronousLoads in
return (size, { [weak self] animation, synchronousLoads, applyInfo in
if let strongSelf = self {
strongSelf.item = item
apply(animation, synchronousLoads)
apply(animation, synchronousLoads, applyInfo)
strongSelf.contentNode.frame = CGRect(origin: CGPoint(), size: size)
}

View File

@ -89,7 +89,7 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
fatalError("init(coder:) has not been implemented")
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let interactiveFileLayout = self.interactiveFileNode.asyncLayout()
return { item, layoutConstants, preparePosition, selection, constrainedSize in
@ -164,13 +164,13 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
}
}
return (CGSize(width: fileSize.width + layoutConstants.file.bubbleInsets.left + layoutConstants.file.bubbleInsets.right, height: fileSize.height + layoutConstants.file.bubbleInsets.top + bottomInset), { [weak self] animation, synchronousLoads in
return (CGSize(width: fileSize.width + layoutConstants.file.bubbleInsets.left + layoutConstants.file.bubbleInsets.right, height: fileSize.height + layoutConstants.file.bubbleInsets.top + bottomInset), { [weak self] animation, synchronousLoads, applyInfo in
if let strongSelf = self {
strongSelf.item = item
strongSelf.interactiveFileNode.frame = CGRect(origin: CGPoint(x: layoutConstants.file.bubbleInsets.left, y: layoutConstants.file.bubbleInsets.top), size: fileSize)
fileApply(synchronousLoads, animation)
fileApply(synchronousLoads, animation, applyInfo)
}
})
})
@ -220,6 +220,13 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
return super.tapActionAtPoint(point, gesture: gesture, isEstimating: isEstimating)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let result = self.interactiveFileNode.hitTest(self.view.convert(point, to: self.interactiveFileNode.view), with: event) {
return result
}
return super.hitTest(point, with: event)
}
override func reactionTargetView(value: String) -> UIView? {
if !self.interactiveFileNode.dateAndStatusNode.isHidden {
return self.interactiveFileNode.dateAndStatusNode.reactionView(value: value)

View File

@ -41,7 +41,7 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode {
fatalError("init(coder:) has not been implemented")
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let contentNodeLayout = self.contentNode.asyncLayout()
return { item, layoutConstants, preparePosition, _, constrainedSize in
@ -87,12 +87,12 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode {
return (refinedWidth, { boundingWidth in
let (size, apply) = finalizeLayout(boundingWidth)
return (size, { [weak self] animation, synchronousLoads in
return (size, { [weak self] animation, synchronousLoads, applyInfo in
if let strongSelf = self {
strongSelf.item = item
strongSelf.game = game
apply(animation, synchronousLoads)
apply(animation, synchronousLoads, applyInfo)
strongSelf.contentNode.frame = CGRect(origin: CGPoint(), size: size)
}

View File

@ -31,11 +31,18 @@ private struct FetchControls {
let cancel: () -> Void
}
private func transcribedText(message: Message) -> EngineAudioTranscriptionResult? {
private enum TranscribedText {
case success(text: String, isPending: Bool)
case error
}
private func transcribedText(message: Message) -> TranscribedText? {
for attribute in message.attributes {
if let attribute = attribute as? AudioTranscriptionMessageAttribute {
if !attribute.text.isEmpty || !attribute.isPending {
return .success(EngineAudioTranscriptionResult.Success(id: attribute.id, text: attribute.text))
if !attribute.text.isEmpty {
return .success(text: attribute.text, isPending: attribute.isPending)
} else {
return .error
}
}
}
@ -128,6 +135,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
private var audioTranscriptionButton: ComponentHostView<Empty>?
private let textNode: TextNode
private let textClippingNode: ASDisplayNode
private var textSelectionNode: TextSelectionNode?
var updateIsTextSelectionActive: ((Bool) -> Void)?
@ -203,6 +211,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
return false
}
}
private var isWaitingForCollapse: Bool = false
override init() {
self.titleNode = TextNode()
@ -240,6 +249,10 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
self.textNode.displaysAsynchronously = false
self.textNode.isUserInteractionEnabled = false
self.textClippingNode = ASDisplayNode()
self.textClippingNode.clipsToBounds = true
self.textClippingNode.addSubnode(self.textNode)
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
self.consumableContentNode = ASImageNode()
@ -330,7 +343,21 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
return
}
if transcribedText(message: message) == nil {
var shouldBeginTranscription = false
var shouldExpandNow = false
if let result = transcribedText(message: message) {
shouldExpandNow = true
if case let .success(_, isPending) = result {
shouldBeginTranscription = isPending
} else {
shouldBeginTranscription = true
}
} else {
shouldBeginTranscription = true
}
if shouldBeginTranscription {
if self.transcribeDisposable == nil {
self.audioTranscriptionState = .inProgress
self.requestUpdateLayout(true)
@ -338,7 +365,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
if context.sharedContext.immediateExperimentalUISettings.localTranscription {
let appLocale = presentationData.strings.baseLanguageCode
let signal: Signal<String?, NoError> = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: message.id))
let signal: Signal<LocallyTranscribedAudio?, NoError> = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: message.id))
|> mapToSignal { message -> Signal<String?, NoError> in
guard let message = message else {
return .single(nil)
@ -363,29 +390,30 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
return TempBox.shared.tempFile(fileName: "audio.m4a").path
})
}
|> mapToSignal { result -> Signal<String?, NoError> in
|> mapToSignal { result -> Signal<LocallyTranscribedAudio?, NoError> in
guard let result = result else {
return .single(nil)
}
return transcribeAudio(path: result, appLocale: appLocale)
}
let _ = signal.start(next: { [weak self] result in
self.transcribeDisposable = (signal
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let strongSelf = self, let arguments = strongSelf.arguments else {
return
}
if let result = result {
let _ = arguments.context.engine.messages.storeLocallyTranscribedAudio(messageId: arguments.message.id, text: result.text, isFinal: result.isFinal).start()
} else {
strongSelf.audioTranscriptionState = .collapsed
strongSelf.requestUpdateLayout(true)
}
}, completed: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.transcribeDisposable = nil
/*if let result = result {
strongSelf.transcribedText = .success(EngineAudioTranscriptionResult.Success(id: 0, text: result))
} else {
strongSelf.transcribedText = .error
}
if strongSelf.transcribedText != nil {
strongSelf.audioTranscriptionState = .expanded
} else {
strongSelf.audioTranscriptionState = .collapsed
}
strongSelf.requestUpdateLayout(true)*/
})
} else {
self.transcribeDisposable = (context.engine.messages.transcribeAudio(messageId: message.id)
@ -394,19 +422,19 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
return
}
strongSelf.transcribeDisposable = nil
/*strongSelf.audioTranscriptionState = .expanded
strongSelf.transcribedText = result
strongSelf.requestUpdateLayout(true)*/
})
}
}
} else {
}
if shouldExpandNow {
switch self.audioTranscriptionState {
case .expanded:
self.audioTranscriptionState = .collapsed
self.isWaitingForCollapse = true
self.requestUpdateLayout(true)
case .collapsed:
self.audioTranscriptionState = .expanded
self.audioTranscriptionState = .inProgress
self.requestUpdateLayout(true)
default:
break
@ -414,7 +442,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
}
func asyncLayout() -> (Arguments) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation) -> Void))) {
func asyncLayout() -> (Arguments) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> Void))) {
let currentFile = self.file
let titleAsyncLayout = TextNode.asyncLayout(self.titleNode)
@ -506,7 +534,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
var isVoice = false
var audioDuration: Int32 = 0
let canTranscribe = arguments.associatedData.isPremium || arguments.context.sharedContext.immediateExperimentalUISettings.localTranscription
let canTranscribe = arguments.associatedData.isPremium
let messageTheme = arguments.incoming ? arguments.presentationData.theme.theme.chat.message.incoming : arguments.presentationData.theme.theme.chat.message.outgoing
@ -615,8 +643,12 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
if let transcribedText = transcribedText, case .expanded = effectiveAudioTranscriptionState {
switch transcribedText {
case let .success(success):
textString = NSAttributedString(string: success.text, font: textFont, textColor: messageTheme.primaryTextColor)
case let .success(text, isPending):
var resultText = text
if isPending {
resultText += " [...]"
}
textString = NSAttributedString(string: resultText, font: textFont, textColor: messageTheme.primaryTextColor)
case .error:
let errorTextFont = Font.regular(floor(arguments.presentationData.fontSize.baseDisplaySize * 15.0 / 17.0))
//TODO:localize
@ -807,7 +839,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
streamingCacheStatusFrame = CGRect()
}
return (fittedLayoutSize, { [weak self] synchronousLoads, animation in
return (fittedLayoutSize, { [weak self] synchronousLoads, animation, info in
if let strongSelf = self {
strongSelf.context = arguments.context
strongSelf.presentationData = arguments.presentationData
@ -825,6 +857,16 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
if let updatedAudioTranscriptionState = updatedAudioTranscriptionState {
strongSelf.audioTranscriptionState = updatedAudioTranscriptionState
switch updatedAudioTranscriptionState {
case .expanded:
info?.setInvertOffsetDirection()
default:
break
}
} else if strongSelf.isWaitingForCollapse {
strongSelf.isWaitingForCollapse = false
info?.setInvertOffsetDirection()
}
if let consumableContentIcon = consumableContentIcon {
@ -849,7 +891,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
if textString == nil, strongSelf.textNode.supernode != nil, animation.isAnimated {
if let snapshotView = strongSelf.textNode.view.snapshotContentTree() {
snapshotView.frame = strongSelf.textNode.frame
strongSelf.view.insertSubview(snapshotView, aboveSubview: strongSelf.textNode.view)
strongSelf.textClippingNode.view.insertSubview(snapshotView, aboveSubview: strongSelf.textNode.view)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
@ -859,24 +901,72 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
let _ = textApply()
let textFrame = CGRect(origin: CGPoint(x: arguments.layoutConstants.text.bubbleInsets.left - arguments.layoutConstants.file.bubbleInsets.left, y: statusReferenceFrame.maxY + 1.0), size: textLayout.size)
strongSelf.textNode.frame = textFrame
let textClippingFrame = CGRect(origin: textFrame.origin, size: CGSize(width: textFrame.width, height: textFrame.height + 8.0))
if textString != nil {
if strongSelf.textNode.supernode == nil {
strongSelf.addSubnode(strongSelf.textNode)
strongSelf.textClippingNode.frame = textClippingFrame
strongSelf.textNode.frame = CGRect(origin: CGPoint(), size: textFrame.size)
if strongSelf.textClippingNode.supernode == nil {
strongSelf.addSubnode(strongSelf.textClippingNode)
if animation.isAnimated {
strongSelf.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
strongSelf.textClippingNode.frame = CGRect(origin: textClippingFrame.origin, size: CGSize(width: textClippingFrame.width, height: 0.0))
animation.animator.updateFrame(layer: strongSelf.textClippingNode.layer, frame: textClippingFrame, completion: nil)
if let maskImage = generateGradientImage(size: CGSize(width: 8.0, height: 10.0), colors: [UIColor.black, UIColor.black, UIColor.clear], locations: [0.0, 0.1, 1.0], direction: .vertical) {
let maskView = UIImageView(image: maskImage.stretchableImage(withLeftCapWidth: 0, topCapHeight: 1))
strongSelf.textClippingNode.view.mask = maskView
maskView.frame = CGRect(origin: CGPoint(), size: CGSize(width: textClippingFrame.width, height: maskImage.size.height))
animation.animator.updateFrame(layer: maskView.layer, frame: CGRect(origin: CGPoint(), size: textClippingFrame.size), completion: { [weak maskView] _ in
maskView?.removeFromSuperview()
guard let strongSelf = self else {
return
}
strongSelf.textClippingNode.view.mask = nil
})
}
}
}
} else {
if strongSelf.textNode.supernode != nil {
strongSelf.textNode.removeFromSupernode()
if strongSelf.textClippingNode.supernode != nil {
if animation.isAnimated {
if let maskImage = generateGradientImage(size: CGSize(width: 8.0, height: 10.0), colors: [UIColor.black, UIColor.black, UIColor.clear], locations: [0.0, 0.1, 1.0], direction: .vertical) {
let maskView = UIImageView(image: maskImage.stretchableImage(withLeftCapWidth: 0, topCapHeight: 1))
maskView.frame = CGRect(origin: CGPoint(), size: strongSelf.textClippingNode.bounds.size)
strongSelf.textClippingNode.view.mask = maskView
animation.animator.updateFrame(layer: maskView.layer, frame: CGRect(origin: CGPoint(), size: CGSize(width: strongSelf.textClippingNode.bounds.width, height: maskImage.size.height)), completion: { [weak maskView] _ in
maskView?.removeFromSuperview()
guard let strongSelf = self else {
return
}
strongSelf.textClippingNode.view.mask = nil
})
}
animation.animator.updateFrame(layer: strongSelf.textClippingNode.layer, frame: CGRect(origin: strongSelf.textClippingNode.frame.origin, size: CGSize(width: strongSelf.textClippingNode.bounds.width, height: 0.0)), completion: nil)
strongSelf.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { completed in
guard let strongSelf = self, completed else {
return
}
strongSelf.textClippingNode.removeFromSupernode()
strongSelf.textNode.layer.removeAllAnimations()
})
} else {
strongSelf.textClippingNode.removeFromSupernode()
}
}
}
if let textSelectionNode = strongSelf.textSelectionNode {
let shouldUpdateLayout = textSelectionNode.frame.size != textFrame.size
textSelectionNode.frame = textFrame
textSelectionNode.highlightAreaNode.frame = textFrame
textSelectionNode.frame = CGRect(origin: CGPoint(), size: textFrame.size)
textSelectionNode.highlightAreaNode.frame = CGRect(origin: CGPoint(), size: textFrame.size)
if shouldUpdateLayout {
textSelectionNode.updateLayout()
}
@ -906,83 +996,6 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
scrubbingFrame.size.width -= 30.0 + 4.0
}
/*if strongSelf.waveformScrubbingNode == nil {
let waveformScrubbingNode = MediaPlayerScrubbingNode(content: .custom(backgroundNode: strongSelf.waveformNode, foregroundContentNode: strongSelf.waveformForegroundNode))
waveformScrubbingNode.hitTestSlop = UIEdgeInsets(top: -10.0, left: 0.0, bottom: -10.0, right: 0.0)
waveformScrubbingNode.seek = { timestamp in
if let strongSelf = self, let context = strongSelf.context, let message = strongSelf.message, let type = peerMessageMediaPlayerType(message) {
context.sharedContext.mediaManager.playlistControl(.seek(timestamp), type: type)
}
}
waveformScrubbingNode.status = strongSelf.playbackStatus.get()
strongSelf.waveformScrubbingNode = waveformScrubbingNode
strongSelf.addSubnode(waveformScrubbingNode)
}
if case .inProgress = audioTranscriptionState {
if strongSelf.waveformShimmerNode == nil {
let waveformShimmerNode = ShimmerEffectNode()
strongSelf.waveformShimmerNode = waveformShimmerNode
strongSelf.addSubnode(waveformShimmerNode)
let waveformMaskNode = AudioWaveformNode()
strongSelf.waveformMaskNode = waveformMaskNode
waveformShimmerNode.view.mask = waveformMaskNode.view
}
if let audioWaveform = audioWaveform, let waveformShimmerNode = strongSelf.waveformShimmerNode, let waveformMaskNode = strongSelf.waveformMaskNode {
waveformShimmerNode.frame = scrubbingFrame
waveformShimmerNode.updateAbsoluteRect(scrubbingFrame, within: CGSize(width: scrubbingFrame.size.width + 60.0, height: scrubbingFrame.size.height + 4.0))
var shapes: [ShimmerEffectNode.Shape] = []
shapes.append(.rect(rect: CGRect(origin: CGPoint(), size: scrubbingFrame.size)))
waveformShimmerNode.update(
backgroundColor: .blue,
foregroundColor: messageTheme.mediaInactiveControlColor,
shimmeringColor: messageTheme.mediaActiveControlColor,
shapes: shapes,
horizontal: true,
effectSize: 60.0,
globalTimeOffset: false,
duration: 0.7,
size: scrubbingFrame.size
)
waveformMaskNode.frame = CGRect(origin: CGPoint(), size: scrubbingFrame.size)
waveformMaskNode.setup(color: .black, gravity: .bottom, waveform: audioWaveform)
}
} else {
if let waveformShimmerNode = strongSelf.waveformShimmerNode {
strongSelf.waveformShimmerNode = nil
if animation.isAnimated {
waveformShimmerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak waveformShimmerNode] _ in
waveformShimmerNode?.removeFromSupernode()
})
} else {
waveformShimmerNode.removeFromSupernode()
}
}
strongSelf.waveformMaskNode = nil
}
if let waveformScrubbingNode = strongSelf.waveformScrubbingNode {
waveformScrubbingNode.frame = scrubbingFrame
//animation.animator.updateFrame(layer: waveformScrubbingNode.layer, frame: scrubbingFrame, completion: nil)
//waveformScrubbingNode.update(size: scrubbingFrame.size, animator: animation.animator)
}
let waveformColor: UIColor
if arguments.incoming {
if consumableContentIcon != nil {
waveformColor = messageTheme.mediaActiveControlColor
} else {
waveformColor = messageTheme.mediaInactiveControlColor
}
} else {
waveformColor = messageTheme.mediaInactiveControlColor
}
strongSelf.waveformNode.setup(color: waveformColor, gravity: .bottom, waveform: audioWaveform)
strongSelf.waveformForegroundNode.setup(color: messageTheme.mediaActiveControlColor, gravity: .bottom, waveform: audioWaveform)*/
let waveformView: ComponentHostView<Empty>
let waveformTransition: Transition
if let current = strongSelf.waveformView {
@ -1070,10 +1083,6 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
}
} else {
/*if let waveformScrubbingNode = strongSelf.waveformScrubbingNode {
strongSelf.waveformScrubbingNode = nil
waveformScrubbingNode.removeFromSupernode()
}*/
if let waveformView = strongSelf.waveformView {
strongSelf.waveformView = nil
waveformView.removeFromSuperview()
@ -1144,7 +1153,6 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}))
}
//strongSelf.waveformNode.displaysAsynchronously = !arguments.presentationData.isPreview
strongSelf.statusNode?.displaysAsynchronously = !arguments.presentationData.isPreview
strongSelf.statusNode?.frame = CGRect(origin: CGPoint(), size: progressFrame.size)
@ -1530,12 +1538,12 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
self.fetchingCompactTextNode.frame = CGRect(origin: self.descriptionNode.frame.origin, size: fetchingCompactSize)
}
static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (Arguments) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation) -> ChatMessageInteractiveFileNode))) {
static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (Arguments) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> ChatMessageInteractiveFileNode))) {
let currentAsyncLayout = node?.asyncLayout()
return { arguments in
var fileNode: ChatMessageInteractiveFileNode
var fileLayout: (Arguments) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation) -> Void)))
var fileLayout: (Arguments) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> Void)))
if let node = node, let currentAsyncLayout = currentAsyncLayout {
fileNode = node
@ -1553,8 +1561,8 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
return (finalWidth, { boundingWidth in
let (finalSize, apply) = finalLayout(boundingWidth)
return (finalSize, { synchronousLoads, animation in
apply(synchronousLoads, animation)
return (finalSize, { synchronousLoads, animation, applyInfo in
apply(synchronousLoads, animation, applyInfo)
return fileNode
})
})
@ -1577,7 +1585,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
func updateIsExtractedToContextPreview(_ value: Bool) {
if value {
if self.textSelectionNode == nil, self.textNode.supernode != nil, let item = self.arguments, !item.associatedData.isCopyProtectionEnabled && !item.message.isCopyProtected(), let rootNode = item.controllerInteraction.chatControllerNode() {
if self.textSelectionNode == nil, self.textClippingNode.supernode != nil, let item = self.arguments, !item.associatedData.isCopyProtectionEnabled && !item.message.isCopyProtected(), let rootNode = item.controllerInteraction.chatControllerNode() {
let selectionColor: UIColor
let knobColor: UIColor
if item.message.effectivelyIncoming(item.context.account.peerId) {
@ -1599,8 +1607,8 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
item.controllerInteraction.performTextSelectionAction(item.message.stableId, text, action)
})
self.textSelectionNode = textSelectionNode
self.addSubnode(textSelectionNode)
self.insertSubnode(textSelectionNode.highlightAreaNode, belowSubnode: self.textNode)
self.textClippingNode.addSubnode(textSelectionNode)
self.textClippingNode.insertSubnode(textSelectionNode.highlightAreaNode, belowSubnode: self.textNode)
textSelectionNode.frame = self.textNode.frame
textSelectionNode.highlightAreaNode.frame = self.textNode.frame
}

View File

@ -34,7 +34,7 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode {
fatalError("init(coder:) has not been implemented")
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let contentNodeLayout = self.contentNode.asyncLayout()
return { item, layoutConstants, preparePosition, _, constrainedSize in
@ -83,12 +83,12 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode {
return (refinedWidth, { boundingWidth in
let (size, apply) = finalizeLayout(boundingWidth)
return (size, { [weak self] animation, synchronousLoads in
return (size, { [weak self] animation, synchronousLoads, applyInfo in
if let strongSelf = self {
strongSelf.item = item
strongSelf.invoice = invoice
apply(animation, synchronousLoads)
apply(animation, synchronousLoads, applyInfo)
strongSelf.contentNode.frame = CGRect(origin: CGPoint(), size: size)
}

View File

@ -64,7 +64,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
self.view.addGestureRecognizer(tapRecognizer)
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let makeImageLayout = self.imageNode.asyncLayout()
let makePinLayout = self.pinNode.asyncLayout()
let statusLayout = self.dateAndStatusNode.asyncLayout()
@ -302,7 +302,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
let imageApply = makeImageLayout(arguments)
return (layoutSize, { [weak self] animation, _ in
return (layoutSize, { [weak self] animation, _, _ in
if let strongSelf = self {
strongSelf.item = item
strongSelf.media = selectedMedia

View File

@ -71,7 +71,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
fatalError("init(coder:) has not been implemented")
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let interactiveImageLayout = self.interactiveImageNode.asyncLayout()
return { item, layoutConstants, preparePosition, selection, constrainedSize in
@ -247,7 +247,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
let layoutSize = CGSize(width: layoutWidth, height: imageLayoutSize.height)
return (layoutSize, { [weak self] animation, synchronousLoads in
return (layoutSize, { [weak self] animation, synchronousLoads, _ in
if let strongSelf = self {
strongSelf.item = item
strongSelf.media = selectedMedia

View File

@ -977,7 +977,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let makeTextLayout = TextNode.asyncLayout(self.textNode)
let makeTypeLayout = TextNode.asyncLayout(self.typeNode)
let makeVotersLayout = TextNode.asyncLayout(self.votersNode)
@ -1309,7 +1309,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
let buttonSubmitActiveTextFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonSubmitActiveTextLayout.size.width) / 2.0), y: optionsButtonSpacing), size: buttonSubmitActiveTextLayout.size)
let buttonViewResultsTextFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonViewResultsTextLayout.size.width) / 2.0), y: optionsButtonSpacing), size: buttonViewResultsTextLayout.size)
return (resultSize, { [weak self] animation, synchronousLoad in
return (resultSize, { [weak self] animation, synchronousLoad, _ in
if let strongSelf = self {
strongSelf.item = item
strongSelf.poll = poll

View File

@ -475,7 +475,7 @@ final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode
fatalError("init(coder:) has not been implemented")
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let buttonsNode = self.buttonsNode
return { item, layoutConstants, preparePosition, _, constrainedSize in
@ -509,7 +509,7 @@ final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
boundingSize.height += topOffset + 2.0
return (boundingSize, { [weak self] animation, synchronousLoad in
return (boundingSize, { [weak self] animation, synchronousLoad, _ in
if let strongSelf = self {
strongSelf.item = item

View File

@ -29,7 +29,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
fatalError("init(coder:) has not been implemented")
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let textLayout = TextNode.asyncLayout(self.textNode)
let statusLayout = self.statusNode.asyncLayout()
@ -151,7 +151,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom
return (boundingSize, { [weak self] animation, _ in
return (boundingSize, { [weak self] animation, _, _ in
if let strongSelf = self {
strongSelf.item = item

View File

@ -151,7 +151,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
fatalError("init(coder:) has not been implemented")
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let textLayout = TextNode.asyncLayout(self.textNode)
let spoilerTextLayout = TextNode.asyncLayout(self.spoilerTextNode)
let statusLayout = self.statusNode.asyncLayout()
@ -486,7 +486,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom
return (boundingSize, { [weak self] animation, _ in
return (boundingSize, { [weak self] animation, _, _ in
if let strongSelf = self {
strongSelf.item = item
if let updatedCachedChatMessageText = updatedCachedChatMessageText {

View File

@ -27,7 +27,7 @@ final class ChatMessageUnsupportedBubbleContentNode: ChatMessageBubbleContentNod
fatalError("init(coder:) has not been implemented")
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let makeButtonLayout = ChatMessageAttachedContentButtonNode.asyncLayout(self.buttonNode)
return { item, layoutConstants, _, _, constrainedSize in
@ -68,7 +68,7 @@ final class ChatMessageUnsupportedBubbleContentNode: ChatMessageBubbleContentNod
actionButtonSizeAndApply = (size, apply)
let adjustedBoundingSize = CGSize(width: refinedButtonWidth + insets.left + insets.right, height: insets.bottom + size.height)
return (adjustedBoundingSize, { [weak self] animation, synchronousLoads in
return (adjustedBoundingSize, { [weak self] animation, synchronousLoads, _ in
if let strongSelf = self {
strongSelf.item = item

View File

@ -102,7 +102,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
fatalError("init(coder:) has not been implemented")
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let contentNodeLayout = self.contentNode.asyncLayout()
return { item, layoutConstants, preparePosition, _, constrainedSize in
@ -378,12 +378,12 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
return (refinedWidth, { boundingWidth in
let (size, apply) = finalizeLayout(boundingWidth)
return (size, { [weak self] animation, synchronousLoads in
return (size, { [weak self] animation, synchronousLoads, applyInfo in
if let strongSelf = self {
strongSelf.item = item
strongSelf.webPage = webPage
apply(animation, synchronousLoads)
apply(animation, synchronousLoads, applyInfo)
strongSelf.contentNode.frame = CGRect(origin: CGPoint(), size: size)
}