diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index 76da5e4752..c53a1effa0 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -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 { diff --git a/submodules/Display/Source/ListViewAnimation.swift b/submodules/Display/Source/ListViewAnimation.swift index 98ffde5ef7..048f94fcd3 100644 --- a/submodules/Display/Source/ListViewAnimation.swift +++ b/submodules/Display/Source/ListViewAnimation.swift @@ -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(from: T, to: T, duration: Double, curve: @escaping (CGFloat) -> CGFloat, beginAt: Double, update: @escaping (CGFloat, T) -> Void, completed: @escaping (Bool) -> Void = { _ in }) { + public init(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) } diff --git a/submodules/Display/Source/ListViewItem.swift b/submodules/Display/Source/ListViewItem.swift index d3c13fbb52..f99829ea00 100644 --- a/submodules/Display/Source/ListViewItem.swift +++ b/submodules/Display/Source/ListViewItem.swift @@ -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 { diff --git a/submodules/Display/Source/ListViewItemNode.swift b/submodules/Display/Source/ListViewItemNode.swift index e8a53198da..54080485d9 100644 --- a/submodules/Display/Source/ListViewItemNode.swift +++ b/submodules/Display/Source/ListViewItemNode.swift @@ -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 { diff --git a/submodules/Media/LocalAudioTranscription/Sources/LocalAudioTranscription.swift b/submodules/Media/LocalAudioTranscription/Sources/LocalAudioTranscription.swift index d2ead4b80f..51244ea2e5 100644 --- a/submodules/Media/LocalAudioTranscription/Sources/LocalAudioTranscription.swift +++ b/submodules/Media/LocalAudioTranscription/Sources/LocalAudioTranscription.swift @@ -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 { @@ -53,7 +54,7 @@ private func transcribeAudio(path: String, locale: String) -> Signal Signal Signal runOn(.mainQueue()) } -public func transcribeAudio(path: String, appLocale: String) -> Signal { +public struct LocallyTranscribedAudio { + public var text: String + public var isFinal: Bool +} + +public func transcribeAudio(path: String, appLocale: String) -> Signal { var signals: [Signal] = [] var locales: [String] = [] if !locales.contains(Locale.current.identifier) { @@ -113,10 +122,12 @@ public func transcribeAudio(path: String, appLocale: String) -> Signal 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) + } } } diff --git a/submodules/MtProtoKit/Sources/MTTcpConnection.m b/submodules/MtProtoKit/Sources/MTTcpConnection.m index d1aac2807b..5108b983c5 100644 --- a/submodules/MtProtoKit/Sources/MTTcpConnection.m +++ b/submodules/MtProtoKit/Sources/MTTcpConnection.m @@ -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]; diff --git a/submodules/Postbox/Sources/MessageHistoryTable.swift b/submodules/Postbox/Sources/MessageHistoryTable.swift index 152abf84b3..b61d5c8e3f 100644 --- a/submodules/Postbox/Sources/MessageHistoryTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTable.swift @@ -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: ×tampBasedMessageAttributesOperations) } - 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: ×tampBasedMessageAttributesOperations) } } } 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() diff --git a/submodules/Postbox/Sources/SeedConfiguration.swift b/submodules/Postbox/Sources/SeedConfiguration.swift index 76f7f678dd..64b9e78a0a 100644 --- a/submodules/Postbox/Sources/SeedConfiguration.swift +++ b/submodules/Postbox/Sources/SeedConfiguration.swift @@ -71,6 +71,7 @@ public final class SeedConfiguration { public let chatMessagesNamespaces: Set public let getGlobalNotificationSettings: (Transaction) -> PostboxGlobalNotificationSettings? public let defaultGlobalNotificationSettings: PostboxGlobalNotificationSettings + public let mergeMessageAttributes: ([MessageAttribute], inout [MessageAttribute]) -> Void public init( globalMessageIdsPeerIdNamespaces: Set, @@ -91,7 +92,8 @@ public final class SeedConfiguration { defaultMessageNamespaceReadStates: [MessageId.Namespace: PeerReadState], chatMessagesNamespaces: Set, 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 } } diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index dc61b08fb9..58930df3ef 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -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 { diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index cfc92c58db..61ffae2899 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -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? ) -> 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 + )) + }) + } + } } } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManager.swift b/submodules/TelegramCore/Sources/State/AccountStateManager.swift index f42199846e..680b32ac81 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManager.swift @@ -64,6 +64,8 @@ public final class AccountStateManager { let auxiliaryMethods: AccountAuxiliaryMethods var transformOutgoingMessageMedia: TransformOutgoingMessageMedia? + let audioTranscriptionManager = Atomic(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 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 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 { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AudioTranscriptionMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AudioTranscriptionMessageAttribute.swift index 0ca15c1388..31dcf80926 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AudioTranscriptionMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AudioTranscriptionMessageAttribute.swift @@ -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) + } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift index 4699366d32..eecc2427fa 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift @@ -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) + } + } + } ) }() diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index fa3764d532..316fb5799b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -329,7 +329,21 @@ public extension TelegramEngine { } public func transcribeAudio(messageId: MessageId) -> Signal { - 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 { + 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 { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift index c7084859e1..e683ef1cf0 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift @@ -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 { +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, messageId: MessageId) -> Signal { 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 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 { 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 in diff --git a/submodules/TelegramUI/Components/AudioWaveformComponent/Sources/AudioWaveformComponent.swift b/submodules/TelegramUI/Components/AudioWaveformComponent/Sources/AudioWaveformComponent.swift index 33235763b4..eca29cf6ec 100644 --- a/submodules/TelegramUI/Components/AudioWaveformComponent/Sources/AudioWaveformComponent.swift +++ b/submodules/TelegramUI/Components/AudioWaveformComponent/Sources/AudioWaveformComponent.swift @@ -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 } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 539a077234..77364a616b 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift index 77c4d2b6a4..92919b03df 100644 --- a/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift index ee635133e5..8941e6cb13 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift @@ -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) diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift index 9f56b3f135..4b0ac10ac8 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleContentNode.swift @@ -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() } diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 1af1065af5..126228d08e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -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, _ 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, _ animation: ListViewItemUpdateAnimation, _ synchronousLoads: Bool, + private static func applyLayout(selfReference: Weak, + _ 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) } diff --git a/submodules/TelegramUI/Sources/ChatMessageCallBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageCallBubbleContentNode.swift index c14d04ee1a..e0f88c2253 100644 --- a/submodules/TelegramUI/Sources/ChatMessageCallBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageCallBubbleContentNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift index 2c833524bf..fb754da2dc 100644 --- a/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageCommentFooterContentNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift index 8c067664b9..9a875ecb03 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageContactBubbleContentNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift index 7bc6f4be86..5d52b3fc0b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift @@ -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) } diff --git a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousLinkContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousLinkContentNode.swift index 5f81fea974..1118997e9c 100644 --- a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousLinkContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousLinkContentNode.swift @@ -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) } diff --git a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousMessageContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousMessageContentNode.swift index 05b3a5f9ec..cbfb47a4ff 100644 --- a/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousMessageContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageEventLogPreviousMessageContentNode.swift @@ -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) } diff --git a/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift index 35f74b7f40..433cc7bc5e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageFileBubbleContentNode.swift @@ -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) diff --git a/submodules/TelegramUI/Sources/ChatMessageGameBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageGameBubbleContentNode.swift index de88910386..b9630d689f 100644 --- a/submodules/TelegramUI/Sources/ChatMessageGameBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageGameBubbleContentNode.swift @@ -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) } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index ce79c1a430..ac19923892 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -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? 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 = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: message.id)) + let signal: Signal = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: message.id)) |> mapToSignal { message -> Signal 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 in + |> mapToSignal { result -> Signal 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 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 } diff --git a/submodules/TelegramUI/Sources/ChatMessageInvoiceBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageInvoiceBubbleContentNode.swift index cf7b7883dd..4f900a526b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInvoiceBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInvoiceBubbleContentNode.swift @@ -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) } diff --git a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift index eb244603e7..022a4122f6 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift index 5cc7bc50fb..3eb9af0c06 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMediaBubbleContentNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift index 232b728e92..1346e494d3 100644 --- a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatMessageReactionsFooterContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageReactionsFooterContentNode.swift index 69430dcc02..d425830a31 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReactionsFooterContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageReactionsFooterContentNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift index 5c0d5dba45..b802d1f7e9 100644 --- a/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageRestrictedBubbleContentNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift index d41b57d92a..5c27588c37 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/ChatMessageUnsupportedBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageUnsupportedBubbleContentNode.swift index db3b94620b..7f6fa7e400 100644 --- a/submodules/TelegramUI/Sources/ChatMessageUnsupportedBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageUnsupportedBubbleContentNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift index eafa243f3e..2c4c0321f9 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -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) }