mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
6827584cd8
@ -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 {
|
||||
|
@ -132,15 +132,17 @@ public final class ListViewAnimation {
|
||||
let to: Interpolatable
|
||||
let duration: Double
|
||||
let startTime: Double
|
||||
let invertOffsetDirection: Bool
|
||||
private let curve: (CGFloat) -> CGFloat
|
||||
private let interpolator: (Interpolatable, Interpolatable, CGFloat) -> Interpolatable
|
||||
private let update: (CGFloat, Interpolatable) -> Void
|
||||
private let completed: (Bool) -> Void
|
||||
|
||||
public init<T: Interpolatable>(from: T, to: T, duration: Double, curve: @escaping (CGFloat) -> CGFloat, beginAt: Double, update: @escaping (CGFloat, T) -> Void, completed: @escaping (Bool) -> Void = { _ in }) {
|
||||
public init<T: Interpolatable>(from: T, to: T, duration: Double, invertOffsetDirection: Bool = false, curve: @escaping (CGFloat) -> CGFloat, beginAt: Double, update: @escaping (CGFloat, T) -> Void, completed: @escaping (Bool) -> Void = { _ in }) {
|
||||
self.from = from
|
||||
self.to = to
|
||||
self.duration = duration
|
||||
self.invertOffsetDirection = invertOffsetDirection
|
||||
self.curve = curve
|
||||
self.startTime = beginAt
|
||||
self.interpolator = T.interpolator()
|
||||
@ -157,6 +159,7 @@ public final class ListViewAnimation {
|
||||
self.curve = copying.curve
|
||||
self.startTime = copying.startTime
|
||||
self.interpolator = copying.interpolator
|
||||
self.invertOffsetDirection = copying.invertOffsetDirection
|
||||
self.update = { progress, value in
|
||||
update(progress, value as! T)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -7,6 +7,7 @@ private var sharedRecognizers: [String: NSObject] = [:]
|
||||
private struct TranscriptionResult {
|
||||
var text: String
|
||||
var confidence: Float
|
||||
var isFinal: Bool
|
||||
}
|
||||
|
||||
private func transcribeAudio(path: String, locale: String) -> Signal<TranscriptionResult?, NoError> {
|
||||
@ -53,7 +54,7 @@ private func transcribeAudio(path: String, locale: String) -> Signal<Transcripti
|
||||
|
||||
let request = SFSpeechURLRecognitionRequest(url: URL(fileURLWithPath: tempFilePath))
|
||||
request.requiresOnDeviceRecognition = speechRecognizer.supportsOnDeviceRecognition
|
||||
request.shouldReportPartialResults = false
|
||||
request.shouldReportPartialResults = true
|
||||
|
||||
let task = speechRecognizer.recognitionTask(with: request, resultHandler: { result, error in
|
||||
if let result = result {
|
||||
@ -62,8 +63,11 @@ private func transcribeAudio(path: String, locale: String) -> Signal<Transcripti
|
||||
confidence += segment.confidence
|
||||
}
|
||||
confidence /= Float(result.bestTranscription.segments.count)
|
||||
subscriber.putNext(TranscriptionResult(text: result.bestTranscription.formattedString, confidence: confidence))
|
||||
subscriber.putCompletion()
|
||||
subscriber.putNext(TranscriptionResult(text: result.bestTranscription.formattedString, confidence: confidence, isFinal: result.isFinal))
|
||||
|
||||
if result.isFinal {
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
} else {
|
||||
print("transcribeAudio: locale: \(locale), error: \(String(describing: error))")
|
||||
|
||||
@ -91,7 +95,12 @@ private func transcribeAudio(path: String, locale: String) -> Signal<Transcripti
|
||||
|> runOn(.mainQueue())
|
||||
}
|
||||
|
||||
public func transcribeAudio(path: String, appLocale: String) -> Signal<String?, NoError> {
|
||||
public struct LocallyTranscribedAudio {
|
||||
public var text: String
|
||||
public var isFinal: Bool
|
||||
}
|
||||
|
||||
public func transcribeAudio(path: String, appLocale: String) -> Signal<LocallyTranscribedAudio?, NoError> {
|
||||
var signals: [Signal<TranscriptionResult?, NoError>] = []
|
||||
var locales: [String] = []
|
||||
if !locales.contains(Locale.current.identifier) {
|
||||
@ -113,10 +122,12 @@ public func transcribeAudio(path: String, appLocale: String) -> Signal<String?,
|
||||
}
|
||||
|
||||
return resultSignal
|
||||
|> map { results -> String? in
|
||||
|> map { results -> LocallyTranscribedAudio? in
|
||||
let sortedResults = results.compactMap({ $0 }).sorted(by: { lhs, rhs in
|
||||
return lhs.confidence > rhs.confidence
|
||||
})
|
||||
return sortedResults.first?.text
|
||||
return sortedResults.first.flatMap { result -> LocallyTranscribedAudio in
|
||||
return LocallyTranscribedAudio(text: result.text, isFinal: result.isFinal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -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()
|
||||
|
@ -71,6 +71,7 @@ public final class SeedConfiguration {
|
||||
public let chatMessagesNamespaces: Set<MessageId.Namespace>
|
||||
public let getGlobalNotificationSettings: (Transaction) -> PostboxGlobalNotificationSettings?
|
||||
public let defaultGlobalNotificationSettings: PostboxGlobalNotificationSettings
|
||||
public let mergeMessageAttributes: ([MessageAttribute], inout [MessageAttribute]) -> Void
|
||||
|
||||
public init(
|
||||
globalMessageIdsPeerIdNamespaces: Set<GlobalMessageIdsNamespace>,
|
||||
@ -91,7 +92,8 @@ public final class SeedConfiguration {
|
||||
defaultMessageNamespaceReadStates: [MessageId.Namespace: PeerReadState],
|
||||
chatMessagesNamespaces: Set<MessageId.Namespace>,
|
||||
getGlobalNotificationSettings: @escaping (Transaction) -> PostboxGlobalNotificationSettings?,
|
||||
defaultGlobalNotificationSettings: PostboxGlobalNotificationSettings
|
||||
defaultGlobalNotificationSettings: PostboxGlobalNotificationSettings,
|
||||
mergeMessageAttributes: @escaping ([MessageAttribute], inout [MessageAttribute]) -> Void
|
||||
) {
|
||||
self.globalMessageIdsPeerIdNamespaces = globalMessageIdsPeerIdNamespaces
|
||||
self.initializeChatListWithHole = initializeChatListWithHole
|
||||
@ -108,5 +110,6 @@ public final class SeedConfiguration {
|
||||
self.chatMessagesNamespaces = chatMessagesNamespaces
|
||||
self.getGlobalNotificationSettings = getGlobalNotificationSettings
|
||||
self.defaultGlobalNotificationSettings = defaultGlobalNotificationSettings
|
||||
self.mergeMessageAttributes = mergeMessageAttributes
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -1101,8 +1101,9 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
||||
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
||||
}
|
||||
}
|
||||
/*case let .updateTranscribeAudio(flags, transcriptionId, text):
|
||||
break*/
|
||||
case let .updateTranscribeAudio(flags, transcriptionId, text):
|
||||
let isPending = (flags & (1 << 0)) != 0
|
||||
updatedState.updateAudioTranscription(id: transcriptionId, isPending: isPending, text: text)
|
||||
case let .updateNotifySettings(apiPeer, apiNotificationSettings):
|
||||
switch apiPeer {
|
||||
case let .notifyPeer(peer):
|
||||
@ -2321,7 +2322,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
|
||||
var currentAddScheduledMessages: OptimizeAddMessagesState?
|
||||
for operation in operations {
|
||||
switch operation {
|
||||
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots:
|
||||
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription:
|
||||
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
|
||||
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
||||
}
|
||||
@ -2402,7 +2403,8 @@ func replayFinalState(
|
||||
auxiliaryMethods: AccountAuxiliaryMethods,
|
||||
finalState: AccountFinalState,
|
||||
removePossiblyDeliveredMessagesUniqueIds: [Int64: PeerId],
|
||||
ignoreDate: Bool
|
||||
ignoreDate: Bool,
|
||||
audioTranscriptionManager: Atomic<AudioTranscriptionManager>?
|
||||
) -> AccountReplayedFinalState? {
|
||||
let verified = verifyTransaction(transaction, finalState: finalState.state)
|
||||
if !verified {
|
||||
@ -3342,6 +3344,48 @@ func replayFinalState(
|
||||
})
|
||||
case .UpdateAttachMenuBots:
|
||||
syncAttachMenuBots = true
|
||||
case let .UpdateAudioTranscription(id, isPending, text):
|
||||
if let audioTranscriptionManager = audioTranscriptionManager {
|
||||
if let messageId = audioTranscriptionManager.with({ audioTranscriptionManager in
|
||||
return audioTranscriptionManager.getPendingMapping(transcriptionId: id)
|
||||
}) {
|
||||
transaction.updateMessage(messageId, update: { currentMessage in
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
|
||||
}
|
||||
var attributes = currentMessage.attributes
|
||||
var found = false
|
||||
loop: for j in 0 ..< attributes.count {
|
||||
if let attribute = attributes[j] as? AudioTranscriptionMessageAttribute {
|
||||
attributes[j] = AudioTranscriptionMessageAttribute(id: id, text: text, isPending: isPending, didRate: attribute.didRate)
|
||||
found = true
|
||||
break loop
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
attributes.append(AudioTranscriptionMessageAttribute(id: id, text: text, isPending: isPending, didRate: false))
|
||||
}
|
||||
|
||||
return .update(StoreMessage(
|
||||
id: currentMessage.id,
|
||||
globallyUniqueId: currentMessage.globallyUniqueId,
|
||||
groupingKey: currentMessage.groupingKey,
|
||||
threadId: currentMessage.threadId,
|
||||
timestamp: currentMessage.timestamp,
|
||||
flags: StoreMessageFlags(currentMessage.flags),
|
||||
tags: currentMessage.tags,
|
||||
globalTags: currentMessage.globalTags,
|
||||
localTags: currentMessage.localTags,
|
||||
forwardInfo: storeForwardInfo,
|
||||
authorId: currentMessage.author?.id,
|
||||
text: currentMessage.text,
|
||||
attributes: attributes,
|
||||
media: currentMessage.media
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,6 +64,8 @@ public final class AccountStateManager {
|
||||
let auxiliaryMethods: AccountAuxiliaryMethods
|
||||
var transformOutgoingMessageMedia: TransformOutgoingMessageMedia?
|
||||
|
||||
let audioTranscriptionManager = Atomic<AudioTranscriptionManager>(value: AudioTranscriptionManager())
|
||||
|
||||
private var updateService: UpdateMessageService?
|
||||
private let updateServiceDisposable = MetaDisposable()
|
||||
|
||||
@ -425,6 +427,7 @@ public final class AccountStateManager {
|
||||
let mediaBox = postbox.mediaBox
|
||||
let accountPeerId = self.accountPeerId
|
||||
let auxiliaryMethods = self.auxiliaryMethods
|
||||
let audioTranscriptionManager = self.audioTranscriptionManager
|
||||
let signal = postbox.stateView()
|
||||
|> mapToSignal { view -> Signal<AuthorizedAccountState, NoError> in
|
||||
if let state = view.state as? AuthorizedAccountState {
|
||||
@ -476,7 +479,7 @@ public final class AccountStateManager {
|
||||
let removePossiblyDeliveredMessagesUniqueIds = self?.removePossiblyDeliveredMessagesUniqueIds ?? Dictionary()
|
||||
return postbox.transaction { transaction -> (difference: Api.updates.Difference?, finalStatte: AccountReplayedFinalState?, skipBecauseOfError: Bool) in
|
||||
let startTime = CFAbsoluteTimeGetCurrent()
|
||||
let replayedState = replayFinalState(accountManager: accountManager, postbox: postbox, accountPeerId: accountPeerId, mediaBox: mediaBox, encryptionProvider: network.encryptionProvider, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState, removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds, ignoreDate: false)
|
||||
let replayedState = replayFinalState(accountManager: accountManager, postbox: postbox, accountPeerId: accountPeerId, mediaBox: mediaBox, encryptionProvider: network.encryptionProvider, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState, removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds, ignoreDate: false, audioTranscriptionManager: audioTranscriptionManager)
|
||||
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime
|
||||
if deltaTime > 1.0 {
|
||||
Logger.shared.log("State", "replayFinalState took \(deltaTime)s")
|
||||
@ -575,6 +578,7 @@ public final class AccountStateManager {
|
||||
let auxiliaryMethods = self.auxiliaryMethods
|
||||
let accountPeerId = self.accountPeerId
|
||||
let mediaBox = postbox.mediaBox
|
||||
let audioTranscriptionManager = self.audioTranscriptionManager
|
||||
let queue = self.queue
|
||||
let signal = initialStateWithUpdateGroups(postbox: postbox, groups: groups)
|
||||
|> mapToSignal { [weak self] state -> Signal<(AccountReplayedFinalState?, AccountFinalState), NoError> in
|
||||
@ -594,7 +598,7 @@ public final class AccountStateManager {
|
||||
return nil
|
||||
} else {
|
||||
let startTime = CFAbsoluteTimeGetCurrent()
|
||||
let result = replayFinalState(accountManager: accountManager, postbox: postbox, accountPeerId: accountPeerId, mediaBox: mediaBox, encryptionProvider: network.encryptionProvider, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState, removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds, ignoreDate: false)
|
||||
let result = replayFinalState(accountManager: accountManager, postbox: postbox, accountPeerId: accountPeerId, mediaBox: mediaBox, encryptionProvider: network.encryptionProvider, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState, removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds, ignoreDate: false, audioTranscriptionManager: audioTranscriptionManager)
|
||||
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime
|
||||
if deltaTime > 1.0 {
|
||||
Logger.shared.log("State", "replayFinalState took \(deltaTime)s")
|
||||
@ -827,10 +831,11 @@ public final class AccountStateManager {
|
||||
let mediaBox = self.postbox.mediaBox
|
||||
let network = self.network
|
||||
let auxiliaryMethods = self.auxiliaryMethods
|
||||
let audioTranscriptionManager = self.audioTranscriptionManager
|
||||
let removePossiblyDeliveredMessagesUniqueIds = self.removePossiblyDeliveredMessagesUniqueIds
|
||||
let signal = self.postbox.transaction { transaction -> AccountReplayedFinalState? in
|
||||
let startTime = CFAbsoluteTimeGetCurrent()
|
||||
let result = replayFinalState(accountManager: accountManager, postbox: postbox, accountPeerId: accountPeerId, mediaBox: mediaBox, encryptionProvider: network.encryptionProvider, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState, removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds, ignoreDate: false)
|
||||
let result = replayFinalState(accountManager: accountManager, postbox: postbox, accountPeerId: accountPeerId, mediaBox: mediaBox, encryptionProvider: network.encryptionProvider, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState, removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds, ignoreDate: false, audioTranscriptionManager: audioTranscriptionManager)
|
||||
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime
|
||||
if deltaTime > 1.0 {
|
||||
Logger.shared.log("State", "replayFinalState took \(deltaTime)s")
|
||||
@ -872,10 +877,11 @@ public final class AccountStateManager {
|
||||
let mediaBox = self.postbox.mediaBox
|
||||
let network = self.network
|
||||
let auxiliaryMethods = self.auxiliaryMethods
|
||||
let audioTranscriptionManager = self.audioTranscriptionManager
|
||||
let removePossiblyDeliveredMessagesUniqueIds = self.removePossiblyDeliveredMessagesUniqueIds
|
||||
let signal = self.postbox.transaction { transaction -> AccountReplayedFinalState? in
|
||||
let startTime = CFAbsoluteTimeGetCurrent()
|
||||
let result = replayFinalState(accountManager: accountManager, postbox: postbox, accountPeerId: accountPeerId, mediaBox: mediaBox, encryptionProvider: network.encryptionProvider, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState, removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds, ignoreDate: false)
|
||||
let result = replayFinalState(accountManager: accountManager, postbox: postbox, accountPeerId: accountPeerId, mediaBox: mediaBox, encryptionProvider: network.encryptionProvider, transaction: transaction, auxiliaryMethods: auxiliaryMethods, finalState: finalState, removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds, ignoreDate: false, audioTranscriptionManager: audioTranscriptionManager)
|
||||
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime
|
||||
if deltaTime > 1.0 {
|
||||
Logger.shared.log("State", "replayFinalState took \(deltaTime)s")
|
||||
@ -897,6 +903,7 @@ public final class AccountStateManager {
|
||||
let mediaBox = postbox.mediaBox
|
||||
let accountPeerId = self.accountPeerId
|
||||
let auxiliaryMethods = self.auxiliaryMethods
|
||||
let audioTranscriptionManager = self.audioTranscriptionManager
|
||||
|
||||
let signal = postbox.stateView()
|
||||
|> mapToSignal { view -> Signal<AuthorizedAccountState, NoError> in
|
||||
@ -959,7 +966,8 @@ public final class AccountStateManager {
|
||||
auxiliaryMethods: auxiliaryMethods,
|
||||
finalState: finalState,
|
||||
removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds,
|
||||
ignoreDate: true
|
||||
ignoreDate: true,
|
||||
audioTranscriptionManager: audioTranscriptionManager
|
||||
)
|
||||
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime
|
||||
if deltaTime > 1.0 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}()
|
||||
|
||||
|
@ -329,7 +329,21 @@ public extension TelegramEngine {
|
||||
}
|
||||
|
||||
public func transcribeAudio(messageId: MessageId) -> Signal<EngineAudioTranscriptionResult, NoError> {
|
||||
return _internal_transcribeAudio(postbox: self.account.postbox, network: self.account.network, messageId: messageId)
|
||||
return _internal_transcribeAudio(postbox: self.account.postbox, network: self.account.network, audioTranscriptionManager: self.account.stateManager.audioTranscriptionManager, messageId: messageId)
|
||||
}
|
||||
|
||||
public func storeLocallyTranscribedAudio(messageId: MessageId, text: String, isFinal: Bool) -> Signal<Never, NoError> {
|
||||
return self.account.postbox.transaction { transaction -> Void in
|
||||
transaction.updateMessage(messageId, update: { currentMessage in
|
||||
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
||||
var attributes = currentMessage.attributes.filter { !($0 is AudioTranscriptionMessageAttribute) }
|
||||
|
||||
attributes.append(AudioTranscriptionMessageAttribute(id: 0, text: text, isPending: !isFinal, didRate: false))
|
||||
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||
})
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public func rateAudioTranscription(messageId: MessageId, id: Int64, isGood: Bool) -> Signal<Never, NoError> {
|
||||
|
@ -30,21 +30,26 @@ func _internal_translate(network: Network, text: String, fromLang: String?, toLa
|
||||
}
|
||||
|
||||
public enum EngineAudioTranscriptionResult {
|
||||
public struct Success {
|
||||
public var id: Int64
|
||||
public var text: String
|
||||
|
||||
public init(id: Int64, text: String) {
|
||||
self.id = id
|
||||
self.text = text
|
||||
}
|
||||
}
|
||||
|
||||
case success(Success)
|
||||
case success
|
||||
case error
|
||||
}
|
||||
|
||||
func _internal_transcribeAudio(postbox: Postbox, network: Network, messageId: MessageId) -> Signal<EngineAudioTranscriptionResult, NoError> {
|
||||
class AudioTranscriptionManager {
|
||||
private var pendingMapping: [Int64: MessageId] = [:]
|
||||
|
||||
init() {
|
||||
}
|
||||
|
||||
func addPendingMapping(transcriptionId: Int64, messageId: MessageId) {
|
||||
self.pendingMapping[transcriptionId] = messageId
|
||||
}
|
||||
|
||||
func getPendingMapping(transcriptionId: Int64) -> MessageId? {
|
||||
return self.pendingMapping[transcriptionId]
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_transcribeAudio(postbox: Postbox, network: Network, audioTranscriptionManager: Atomic<AudioTranscriptionManager>, messageId: MessageId) -> Signal<EngineAudioTranscriptionResult, NoError> {
|
||||
return postbox.transaction { transaction -> Api.InputPeer? in
|
||||
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
|
||||
}
|
||||
@ -58,25 +63,38 @@ func _internal_transcribeAudio(postbox: Postbox, network: Network, messageId: Me
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<EngineAudioTranscriptionResult, NoError> in
|
||||
guard let result = result else {
|
||||
return .single(.error)
|
||||
}
|
||||
|
||||
return postbox.transaction { transaction -> EngineAudioTranscriptionResult in
|
||||
switch result {
|
||||
case let .transcribedAudio(flags, transcriptionId, text):
|
||||
transaction.updateMessage(messageId, update: { currentMessage in
|
||||
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
||||
var attributes = currentMessage.attributes.filter { !($0 is AudioTranscriptionMessageAttribute) }
|
||||
|
||||
let updatedAttribute: AudioTranscriptionMessageAttribute
|
||||
if let result = result {
|
||||
switch result {
|
||||
case let .transcribedAudio(flags, transcriptionId, text):
|
||||
let isPending = (flags & (1 << 0)) != 0
|
||||
|
||||
attributes.append(AudioTranscriptionMessageAttribute(id: transcriptionId, text: text, isPending: isPending))
|
||||
if isPending {
|
||||
audioTranscriptionManager.with { audioTranscriptionManager in
|
||||
audioTranscriptionManager.addPendingMapping(transcriptionId: transcriptionId, messageId: messageId)
|
||||
}
|
||||
}
|
||||
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||
})
|
||||
updatedAttribute = AudioTranscriptionMessageAttribute(id: transcriptionId, text: text, isPending: isPending, didRate: false)
|
||||
}
|
||||
} else {
|
||||
updatedAttribute = AudioTranscriptionMessageAttribute(id: 0, text: "", isPending: false, didRate: false)
|
||||
}
|
||||
|
||||
return .success(EngineAudioTranscriptionResult.Success(id: transcriptionId, text: text))
|
||||
transaction.updateMessage(messageId, update: { currentMessage in
|
||||
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
||||
var attributes = currentMessage.attributes.filter { !($0 is AudioTranscriptionMessageAttribute) }
|
||||
|
||||
attributes.append(updatedAttribute)
|
||||
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||
})
|
||||
|
||||
if let _ = result {
|
||||
return .success
|
||||
} else {
|
||||
return .error
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -85,6 +103,35 @@ func _internal_transcribeAudio(postbox: Postbox, network: Network, messageId: Me
|
||||
|
||||
func _internal_rateAudioTranscription(postbox: Postbox, network: Network, messageId: MessageId, id: Int64, isGood: Bool) -> Signal<Never, NoError> {
|
||||
return postbox.transaction { transaction -> Api.InputPeer? in
|
||||
transaction.updateMessage(messageId, update: { currentMessage in
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
|
||||
}
|
||||
var attributes = currentMessage.attributes
|
||||
for i in 0 ..< attributes.count {
|
||||
if let attribute = attributes[i] as? AudioTranscriptionMessageAttribute {
|
||||
attributes[i] = attribute.withDidRate()
|
||||
}
|
||||
}
|
||||
return .update(StoreMessage(
|
||||
id: currentMessage.id,
|
||||
globallyUniqueId: currentMessage.globallyUniqueId,
|
||||
groupingKey: currentMessage.groupingKey,
|
||||
threadId: currentMessage.threadId,
|
||||
timestamp: currentMessage.timestamp,
|
||||
flags: StoreMessageFlags(currentMessage.flags),
|
||||
tags: currentMessage.tags,
|
||||
globalTags: currentMessage.globalTags,
|
||||
localTags: currentMessage.localTags,
|
||||
forwardInfo: storeForwardInfo,
|
||||
authorId: currentMessage.author?.id,
|
||||
text: currentMessage.text,
|
||||
attributes: attributes,
|
||||
media: currentMessage.media
|
||||
))
|
||||
})
|
||||
|
||||
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
|
||||
}
|
||||
|> mapToSignal { inputPeer -> Signal<Never, NoError> in
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -986,7 +986,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
|
||||
override func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
||||
var currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))] = []
|
||||
var currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))] = []
|
||||
for contentNode in self.contentNodes {
|
||||
if let message = contentNode.item?.message {
|
||||
currentContentClassesPropertiesAndLayouts.append((message, type(of: contentNode) as AnyClass, contentNode.supportsMosaic, contentNode.asyncLayoutContent()))
|
||||
@ -1033,7 +1033,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
|
||||
private static func beginLayout(selfReference: Weak<ChatMessageBubbleItemNode>, _ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool,
|
||||
currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))],
|
||||
currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))],
|
||||
authorNameLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
|
||||
adminBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
|
||||
forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
|
||||
@ -1277,7 +1277,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
|
||||
let maximumContentWidth = floor(tmpWidth - layoutConstants.bubble.edgeInset - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - layoutConstants.bubble.contentInsets.right - avatarInset)
|
||||
|
||||
var contentPropertiesAndPrepareLayouts: [(Message, Bool, ChatMessageEntryAttributes, BubbleItemAttributes, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))] = []
|
||||
var contentPropertiesAndPrepareLayouts: [(Message, Bool, ChatMessageEntryAttributes, BubbleItemAttributes, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))] = []
|
||||
var addedContentNodes: [(Message, Bool, ChatMessageBubbleContentNode)]?
|
||||
|
||||
let (contentNodeMessagesAndClasses, needSeparateContainers, needReactions) = contentNodeMessagesAndClassesForItem(item)
|
||||
@ -1286,7 +1286,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
|
||||
var found = false
|
||||
for currentNodeItemValue in currentContentClassesPropertiesAndLayouts {
|
||||
let currentNodeItem = currentNodeItemValue as (message: Message, type: AnyClass, supportsMosaic: Bool, currentLayout: (ChatMessageBubbleContentItem, ChatMessageItemLayoutConstants, ChatMessageBubblePreparePosition, Bool?, CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))
|
||||
let currentNodeItem = currentNodeItemValue as (message: Message, type: AnyClass, supportsMosaic: Bool, currentLayout: (ChatMessageBubbleContentItem, ChatMessageItemLayoutConstants, ChatMessageBubblePreparePosition, Bool?, CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))
|
||||
|
||||
if currentNodeItem.type == contentNodeItem.type && currentNodeItem.message.stableId == contentNodeItem.message.stableId {
|
||||
contentPropertiesAndPrepareLayouts.append((contentNodeItem.message, currentNodeItem.supportsMosaic, contentNodeItem.attributes, contentNodeItem.bubbleAttributes, currentNodeItem.currentLayout))
|
||||
@ -1357,7 +1357,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
inlineBotNameString = nil
|
||||
}
|
||||
|
||||
var contentPropertiesAndLayouts: [(CGSize?, ChatMessageBubbleContentProperties, ChatMessageBubblePreparePosition, BubbleItemAttributes, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void)), UInt32?, Bool?)] = []
|
||||
var contentPropertiesAndLayouts: [(CGSize?, ChatMessageBubbleContentProperties, ChatMessageBubblePreparePosition, BubbleItemAttributes, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void)), UInt32?, Bool?)] = []
|
||||
|
||||
var backgroundHiding: ChatMessageBubbleContentBackgroundHiding?
|
||||
var hasSolidWallpaper = false
|
||||
@ -1845,7 +1845,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
}
|
||||
|
||||
var contentNodePropertiesAndFinalize: [(ChatMessageBubbleContentProperties, ChatMessageBubbleContentPosition?, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void), UInt32?, Bool?)] = []
|
||||
var contentNodePropertiesAndFinalize: [(ChatMessageBubbleContentProperties, ChatMessageBubbleContentPosition?, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void), UInt32?, Bool?)] = []
|
||||
|
||||
var maxContentWidth: CGFloat = headerSize.width
|
||||
|
||||
@ -2031,7 +2031,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
|
||||
var contentSize = CGSize(width: maxContentWidth, height: 0.0)
|
||||
var contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, Bool, (ListViewItemUpdateAnimation, Bool) -> Void)] = []
|
||||
var contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, Bool, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void)] = []
|
||||
var contentContainerNodeFrames: [(UInt32, CGRect, Bool?, CGFloat)] = []
|
||||
var currentContainerGroupId: UInt32?
|
||||
var currentItemSelection: Bool?
|
||||
@ -2245,7 +2245,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
})
|
||||
}
|
||||
|
||||
private static func applyLayout(selfReference: Weak<ChatMessageBubbleItemNode>, _ animation: ListViewItemUpdateAnimation, _ synchronousLoads: Bool,
|
||||
private static func applyLayout(selfReference: Weak<ChatMessageBubbleItemNode>,
|
||||
_ animation: ListViewItemUpdateAnimation,
|
||||
_ synchronousLoads: Bool,
|
||||
params: ListViewItemLayoutParams,
|
||||
applyInfo: ListViewItemApply,
|
||||
layout: ListViewItemNodeLayout,
|
||||
@ -2278,7 +2280,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
removedContentNodeIndices: [Int]?,
|
||||
addedContentNodes: [(Message, Bool, ChatMessageBubbleContentNode)]?,
|
||||
contentNodeMessagesAndClasses: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)],
|
||||
contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, Bool, (ListViewItemUpdateAnimation, Bool) -> Void)],
|
||||
contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, Bool, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void)],
|
||||
contentContainerNodeFrames: [(UInt32, CGRect, Bool?, CGFloat)],
|
||||
mosaicStatusOrigin: CGPoint?,
|
||||
mosaicStatusSizeAndApply: (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode)?,
|
||||
@ -2730,7 +2732,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
|
||||
var contentNodeIndex = 0
|
||||
for (relativeFrame, _, useContentOrigin, apply) in contentNodeFramesPropertiesAndApply {
|
||||
apply(animation, synchronousLoads)
|
||||
apply(animation, synchronousLoads, applyInfo)
|
||||
|
||||
if contentNodeIndex >= strongSelf.contentNodes.count {
|
||||
break
|
||||
@ -3495,6 +3497,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
}
|
||||
|
||||
for contentNode in self.contentNodes {
|
||||
if let result = contentNode.hitTest(self.view.convert(point, to: contentNode.view), with: event) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -31,11 +31,18 @@ private struct FetchControls {
|
||||
let cancel: () -> Void
|
||||
}
|
||||
|
||||
private func transcribedText(message: Message) -> EngineAudioTranscriptionResult? {
|
||||
private enum TranscribedText {
|
||||
case success(text: String, isPending: Bool)
|
||||
case error
|
||||
}
|
||||
|
||||
private func transcribedText(message: Message) -> TranscribedText? {
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? AudioTranscriptionMessageAttribute {
|
||||
if !attribute.text.isEmpty || !attribute.isPending {
|
||||
return .success(EngineAudioTranscriptionResult.Success(id: attribute.id, text: attribute.text))
|
||||
if !attribute.text.isEmpty {
|
||||
return .success(text: attribute.text, isPending: attribute.isPending)
|
||||
} else {
|
||||
return .error
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -128,6 +135,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
|
||||
private var audioTranscriptionButton: ComponentHostView<Empty>?
|
||||
private let textNode: TextNode
|
||||
private let textClippingNode: ASDisplayNode
|
||||
private var textSelectionNode: TextSelectionNode?
|
||||
|
||||
var updateIsTextSelectionActive: ((Bool) -> Void)?
|
||||
@ -203,6 +211,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
return false
|
||||
}
|
||||
}
|
||||
private var isWaitingForCollapse: Bool = false
|
||||
|
||||
override init() {
|
||||
self.titleNode = TextNode()
|
||||
@ -240,6 +249,10 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
|
||||
self.textClippingNode = ASDisplayNode()
|
||||
self.textClippingNode.clipsToBounds = true
|
||||
self.textClippingNode.addSubnode(self.textNode)
|
||||
|
||||
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
|
||||
|
||||
self.consumableContentNode = ASImageNode()
|
||||
@ -330,7 +343,21 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
return
|
||||
}
|
||||
|
||||
if transcribedText(message: message) == nil {
|
||||
var shouldBeginTranscription = false
|
||||
var shouldExpandNow = false
|
||||
if let result = transcribedText(message: message) {
|
||||
shouldExpandNow = true
|
||||
|
||||
if case let .success(_, isPending) = result {
|
||||
shouldBeginTranscription = isPending
|
||||
} else {
|
||||
shouldBeginTranscription = true
|
||||
}
|
||||
} else {
|
||||
shouldBeginTranscription = true
|
||||
}
|
||||
|
||||
if shouldBeginTranscription {
|
||||
if self.transcribeDisposable == nil {
|
||||
self.audioTranscriptionState = .inProgress
|
||||
self.requestUpdateLayout(true)
|
||||
@ -338,7 +365,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
if context.sharedContext.immediateExperimentalUISettings.localTranscription {
|
||||
let appLocale = presentationData.strings.baseLanguageCode
|
||||
|
||||
let signal: Signal<String?, NoError> = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: message.id))
|
||||
let signal: Signal<LocallyTranscribedAudio?, NoError> = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: message.id))
|
||||
|> mapToSignal { message -> Signal<String?, NoError> in
|
||||
guard let message = message else {
|
||||
return .single(nil)
|
||||
@ -363,29 +390,30 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
return TempBox.shared.tempFile(fileName: "audio.m4a").path
|
||||
})
|
||||
}
|
||||
|> mapToSignal { result -> Signal<String?, NoError> in
|
||||
|> mapToSignal { result -> Signal<LocallyTranscribedAudio?, NoError> in
|
||||
guard let result = result else {
|
||||
return .single(nil)
|
||||
}
|
||||
return transcribeAudio(path: result, appLocale: appLocale)
|
||||
}
|
||||
|
||||
let _ = signal.start(next: { [weak self] result in
|
||||
self.transcribeDisposable = (signal
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
guard let strongSelf = self, let arguments = strongSelf.arguments else {
|
||||
return
|
||||
}
|
||||
|
||||
if let result = result {
|
||||
let _ = arguments.context.engine.messages.storeLocallyTranscribedAudio(messageId: arguments.message.id, text: result.text, isFinal: result.isFinal).start()
|
||||
} else {
|
||||
strongSelf.audioTranscriptionState = .collapsed
|
||||
strongSelf.requestUpdateLayout(true)
|
||||
}
|
||||
}, completed: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.transcribeDisposable = nil
|
||||
/*if let result = result {
|
||||
strongSelf.transcribedText = .success(EngineAudioTranscriptionResult.Success(id: 0, text: result))
|
||||
} else {
|
||||
strongSelf.transcribedText = .error
|
||||
}
|
||||
if strongSelf.transcribedText != nil {
|
||||
strongSelf.audioTranscriptionState = .expanded
|
||||
} else {
|
||||
strongSelf.audioTranscriptionState = .collapsed
|
||||
}
|
||||
strongSelf.requestUpdateLayout(true)*/
|
||||
})
|
||||
} else {
|
||||
self.transcribeDisposable = (context.engine.messages.transcribeAudio(messageId: message.id)
|
||||
@ -394,19 +422,19 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
return
|
||||
}
|
||||
strongSelf.transcribeDisposable = nil
|
||||
/*strongSelf.audioTranscriptionState = .expanded
|
||||
strongSelf.transcribedText = result
|
||||
strongSelf.requestUpdateLayout(true)*/
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
if shouldExpandNow {
|
||||
switch self.audioTranscriptionState {
|
||||
case .expanded:
|
||||
self.audioTranscriptionState = .collapsed
|
||||
self.isWaitingForCollapse = true
|
||||
self.requestUpdateLayout(true)
|
||||
case .collapsed:
|
||||
self.audioTranscriptionState = .expanded
|
||||
self.audioTranscriptionState = .inProgress
|
||||
self.requestUpdateLayout(true)
|
||||
default:
|
||||
break
|
||||
@ -414,7 +442,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
func asyncLayout() -> (Arguments) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation) -> Void))) {
|
||||
func asyncLayout() -> (Arguments) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> Void))) {
|
||||
let currentFile = self.file
|
||||
|
||||
let titleAsyncLayout = TextNode.asyncLayout(self.titleNode)
|
||||
@ -506,7 +534,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
var isVoice = false
|
||||
var audioDuration: Int32 = 0
|
||||
|
||||
let canTranscribe = arguments.associatedData.isPremium || arguments.context.sharedContext.immediateExperimentalUISettings.localTranscription
|
||||
let canTranscribe = arguments.associatedData.isPremium
|
||||
|
||||
let messageTheme = arguments.incoming ? arguments.presentationData.theme.theme.chat.message.incoming : arguments.presentationData.theme.theme.chat.message.outgoing
|
||||
|
||||
@ -615,8 +643,12 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
|
||||
if let transcribedText = transcribedText, case .expanded = effectiveAudioTranscriptionState {
|
||||
switch transcribedText {
|
||||
case let .success(success):
|
||||
textString = NSAttributedString(string: success.text, font: textFont, textColor: messageTheme.primaryTextColor)
|
||||
case let .success(text, isPending):
|
||||
var resultText = text
|
||||
if isPending {
|
||||
resultText += " [...]"
|
||||
}
|
||||
textString = NSAttributedString(string: resultText, font: textFont, textColor: messageTheme.primaryTextColor)
|
||||
case .error:
|
||||
let errorTextFont = Font.regular(floor(arguments.presentationData.fontSize.baseDisplaySize * 15.0 / 17.0))
|
||||
//TODO:localize
|
||||
@ -807,7 +839,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
streamingCacheStatusFrame = CGRect()
|
||||
}
|
||||
|
||||
return (fittedLayoutSize, { [weak self] synchronousLoads, animation in
|
||||
return (fittedLayoutSize, { [weak self] synchronousLoads, animation, info in
|
||||
if let strongSelf = self {
|
||||
strongSelf.context = arguments.context
|
||||
strongSelf.presentationData = arguments.presentationData
|
||||
@ -825,6 +857,16 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
|
||||
if let updatedAudioTranscriptionState = updatedAudioTranscriptionState {
|
||||
strongSelf.audioTranscriptionState = updatedAudioTranscriptionState
|
||||
|
||||
switch updatedAudioTranscriptionState {
|
||||
case .expanded:
|
||||
info?.setInvertOffsetDirection()
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else if strongSelf.isWaitingForCollapse {
|
||||
strongSelf.isWaitingForCollapse = false
|
||||
info?.setInvertOffsetDirection()
|
||||
}
|
||||
|
||||
if let consumableContentIcon = consumableContentIcon {
|
||||
@ -849,7 +891,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
if textString == nil, strongSelf.textNode.supernode != nil, animation.isAnimated {
|
||||
if let snapshotView = strongSelf.textNode.view.snapshotContentTree() {
|
||||
snapshotView.frame = strongSelf.textNode.frame
|
||||
strongSelf.view.insertSubview(snapshotView, aboveSubview: strongSelf.textNode.view)
|
||||
strongSelf.textClippingNode.view.insertSubview(snapshotView, aboveSubview: strongSelf.textNode.view)
|
||||
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
@ -859,24 +901,72 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
|
||||
let _ = textApply()
|
||||
let textFrame = CGRect(origin: CGPoint(x: arguments.layoutConstants.text.bubbleInsets.left - arguments.layoutConstants.file.bubbleInsets.left, y: statusReferenceFrame.maxY + 1.0), size: textLayout.size)
|
||||
strongSelf.textNode.frame = textFrame
|
||||
let textClippingFrame = CGRect(origin: textFrame.origin, size: CGSize(width: textFrame.width, height: textFrame.height + 8.0))
|
||||
if textString != nil {
|
||||
if strongSelf.textNode.supernode == nil {
|
||||
strongSelf.addSubnode(strongSelf.textNode)
|
||||
strongSelf.textClippingNode.frame = textClippingFrame
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(), size: textFrame.size)
|
||||
|
||||
if strongSelf.textClippingNode.supernode == nil {
|
||||
strongSelf.addSubnode(strongSelf.textClippingNode)
|
||||
if animation.isAnimated {
|
||||
strongSelf.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
|
||||
strongSelf.textClippingNode.frame = CGRect(origin: textClippingFrame.origin, size: CGSize(width: textClippingFrame.width, height: 0.0))
|
||||
animation.animator.updateFrame(layer: strongSelf.textClippingNode.layer, frame: textClippingFrame, completion: nil)
|
||||
|
||||
if let maskImage = generateGradientImage(size: CGSize(width: 8.0, height: 10.0), colors: [UIColor.black, UIColor.black, UIColor.clear], locations: [0.0, 0.1, 1.0], direction: .vertical) {
|
||||
let maskView = UIImageView(image: maskImage.stretchableImage(withLeftCapWidth: 0, topCapHeight: 1))
|
||||
strongSelf.textClippingNode.view.mask = maskView
|
||||
|
||||
maskView.frame = CGRect(origin: CGPoint(), size: CGSize(width: textClippingFrame.width, height: maskImage.size.height))
|
||||
animation.animator.updateFrame(layer: maskView.layer, frame: CGRect(origin: CGPoint(), size: textClippingFrame.size), completion: { [weak maskView] _ in
|
||||
maskView?.removeFromSuperview()
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.textClippingNode.view.mask = nil
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if strongSelf.textNode.supernode != nil {
|
||||
strongSelf.textNode.removeFromSupernode()
|
||||
if strongSelf.textClippingNode.supernode != nil {
|
||||
if animation.isAnimated {
|
||||
if let maskImage = generateGradientImage(size: CGSize(width: 8.0, height: 10.0), colors: [UIColor.black, UIColor.black, UIColor.clear], locations: [0.0, 0.1, 1.0], direction: .vertical) {
|
||||
let maskView = UIImageView(image: maskImage.stretchableImage(withLeftCapWidth: 0, topCapHeight: 1))
|
||||
maskView.frame = CGRect(origin: CGPoint(), size: strongSelf.textClippingNode.bounds.size)
|
||||
|
||||
strongSelf.textClippingNode.view.mask = maskView
|
||||
|
||||
animation.animator.updateFrame(layer: maskView.layer, frame: CGRect(origin: CGPoint(), size: CGSize(width: strongSelf.textClippingNode.bounds.width, height: maskImage.size.height)), completion: { [weak maskView] _ in
|
||||
maskView?.removeFromSuperview()
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.textClippingNode.view.mask = nil
|
||||
})
|
||||
}
|
||||
|
||||
animation.animator.updateFrame(layer: strongSelf.textClippingNode.layer, frame: CGRect(origin: strongSelf.textClippingNode.frame.origin, size: CGSize(width: strongSelf.textClippingNode.bounds.width, height: 0.0)), completion: nil)
|
||||
|
||||
strongSelf.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { completed in
|
||||
guard let strongSelf = self, completed else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.textClippingNode.removeFromSupernode()
|
||||
strongSelf.textNode.layer.removeAllAnimations()
|
||||
})
|
||||
} else {
|
||||
strongSelf.textClippingNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let textSelectionNode = strongSelf.textSelectionNode {
|
||||
let shouldUpdateLayout = textSelectionNode.frame.size != textFrame.size
|
||||
textSelectionNode.frame = textFrame
|
||||
textSelectionNode.highlightAreaNode.frame = textFrame
|
||||
textSelectionNode.frame = CGRect(origin: CGPoint(), size: textFrame.size)
|
||||
textSelectionNode.highlightAreaNode.frame = CGRect(origin: CGPoint(), size: textFrame.size)
|
||||
if shouldUpdateLayout {
|
||||
textSelectionNode.updateLayout()
|
||||
}
|
||||
@ -906,83 +996,6 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
scrubbingFrame.size.width -= 30.0 + 4.0
|
||||
}
|
||||
|
||||
/*if strongSelf.waveformScrubbingNode == nil {
|
||||
let waveformScrubbingNode = MediaPlayerScrubbingNode(content: .custom(backgroundNode: strongSelf.waveformNode, foregroundContentNode: strongSelf.waveformForegroundNode))
|
||||
waveformScrubbingNode.hitTestSlop = UIEdgeInsets(top: -10.0, left: 0.0, bottom: -10.0, right: 0.0)
|
||||
waveformScrubbingNode.seek = { timestamp in
|
||||
if let strongSelf = self, let context = strongSelf.context, let message = strongSelf.message, let type = peerMessageMediaPlayerType(message) {
|
||||
context.sharedContext.mediaManager.playlistControl(.seek(timestamp), type: type)
|
||||
}
|
||||
}
|
||||
waveformScrubbingNode.status = strongSelf.playbackStatus.get()
|
||||
strongSelf.waveformScrubbingNode = waveformScrubbingNode
|
||||
strongSelf.addSubnode(waveformScrubbingNode)
|
||||
}
|
||||
|
||||
if case .inProgress = audioTranscriptionState {
|
||||
if strongSelf.waveformShimmerNode == nil {
|
||||
let waveformShimmerNode = ShimmerEffectNode()
|
||||
strongSelf.waveformShimmerNode = waveformShimmerNode
|
||||
strongSelf.addSubnode(waveformShimmerNode)
|
||||
|
||||
let waveformMaskNode = AudioWaveformNode()
|
||||
strongSelf.waveformMaskNode = waveformMaskNode
|
||||
waveformShimmerNode.view.mask = waveformMaskNode.view
|
||||
}
|
||||
|
||||
if let audioWaveform = audioWaveform, let waveformShimmerNode = strongSelf.waveformShimmerNode, let waveformMaskNode = strongSelf.waveformMaskNode {
|
||||
waveformShimmerNode.frame = scrubbingFrame
|
||||
waveformShimmerNode.updateAbsoluteRect(scrubbingFrame, within: CGSize(width: scrubbingFrame.size.width + 60.0, height: scrubbingFrame.size.height + 4.0))
|
||||
|
||||
var shapes: [ShimmerEffectNode.Shape] = []
|
||||
shapes.append(.rect(rect: CGRect(origin: CGPoint(), size: scrubbingFrame.size)))
|
||||
waveformShimmerNode.update(
|
||||
backgroundColor: .blue,
|
||||
foregroundColor: messageTheme.mediaInactiveControlColor,
|
||||
shimmeringColor: messageTheme.mediaActiveControlColor,
|
||||
shapes: shapes,
|
||||
horizontal: true,
|
||||
effectSize: 60.0,
|
||||
globalTimeOffset: false,
|
||||
duration: 0.7,
|
||||
size: scrubbingFrame.size
|
||||
)
|
||||
|
||||
waveformMaskNode.frame = CGRect(origin: CGPoint(), size: scrubbingFrame.size)
|
||||
waveformMaskNode.setup(color: .black, gravity: .bottom, waveform: audioWaveform)
|
||||
}
|
||||
} else {
|
||||
if let waveformShimmerNode = strongSelf.waveformShimmerNode {
|
||||
strongSelf.waveformShimmerNode = nil
|
||||
if animation.isAnimated {
|
||||
waveformShimmerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak waveformShimmerNode] _ in
|
||||
waveformShimmerNode?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
waveformShimmerNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
strongSelf.waveformMaskNode = nil
|
||||
}
|
||||
|
||||
if let waveformScrubbingNode = strongSelf.waveformScrubbingNode {
|
||||
waveformScrubbingNode.frame = scrubbingFrame
|
||||
//animation.animator.updateFrame(layer: waveformScrubbingNode.layer, frame: scrubbingFrame, completion: nil)
|
||||
//waveformScrubbingNode.update(size: scrubbingFrame.size, animator: animation.animator)
|
||||
}
|
||||
let waveformColor: UIColor
|
||||
if arguments.incoming {
|
||||
if consumableContentIcon != nil {
|
||||
waveformColor = messageTheme.mediaActiveControlColor
|
||||
} else {
|
||||
waveformColor = messageTheme.mediaInactiveControlColor
|
||||
}
|
||||
} else {
|
||||
waveformColor = messageTheme.mediaInactiveControlColor
|
||||
}
|
||||
strongSelf.waveformNode.setup(color: waveformColor, gravity: .bottom, waveform: audioWaveform)
|
||||
strongSelf.waveformForegroundNode.setup(color: messageTheme.mediaActiveControlColor, gravity: .bottom, waveform: audioWaveform)*/
|
||||
|
||||
let waveformView: ComponentHostView<Empty>
|
||||
let waveformTransition: Transition
|
||||
if let current = strongSelf.waveformView {
|
||||
@ -1070,10 +1083,6 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/*if let waveformScrubbingNode = strongSelf.waveformScrubbingNode {
|
||||
strongSelf.waveformScrubbingNode = nil
|
||||
waveformScrubbingNode.removeFromSupernode()
|
||||
}*/
|
||||
if let waveformView = strongSelf.waveformView {
|
||||
strongSelf.waveformView = nil
|
||||
waveformView.removeFromSuperview()
|
||||
@ -1144,7 +1153,6 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
}))
|
||||
}
|
||||
|
||||
//strongSelf.waveformNode.displaysAsynchronously = !arguments.presentationData.isPreview
|
||||
strongSelf.statusNode?.displaysAsynchronously = !arguments.presentationData.isPreview
|
||||
strongSelf.statusNode?.frame = CGRect(origin: CGPoint(), size: progressFrame.size)
|
||||
|
||||
@ -1530,12 +1538,12 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
self.fetchingCompactTextNode.frame = CGRect(origin: self.descriptionNode.frame.origin, size: fetchingCompactSize)
|
||||
}
|
||||
|
||||
static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (Arguments) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation) -> ChatMessageInteractiveFileNode))) {
|
||||
static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (Arguments) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> ChatMessageInteractiveFileNode))) {
|
||||
let currentAsyncLayout = node?.asyncLayout()
|
||||
|
||||
return { arguments in
|
||||
var fileNode: ChatMessageInteractiveFileNode
|
||||
var fileLayout: (Arguments) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation) -> Void)))
|
||||
var fileLayout: (Arguments) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool, ListViewItemUpdateAnimation, ListViewItemApply?) -> Void)))
|
||||
|
||||
if let node = node, let currentAsyncLayout = currentAsyncLayout {
|
||||
fileNode = node
|
||||
@ -1553,8 +1561,8 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
return (finalWidth, { boundingWidth in
|
||||
let (finalSize, apply) = finalLayout(boundingWidth)
|
||||
|
||||
return (finalSize, { synchronousLoads, animation in
|
||||
apply(synchronousLoads, animation)
|
||||
return (finalSize, { synchronousLoads, animation, applyInfo in
|
||||
apply(synchronousLoads, animation, applyInfo)
|
||||
return fileNode
|
||||
})
|
||||
})
|
||||
@ -1577,7 +1585,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
|
||||
func updateIsExtractedToContextPreview(_ value: Bool) {
|
||||
if value {
|
||||
if self.textSelectionNode == nil, self.textNode.supernode != nil, let item = self.arguments, !item.associatedData.isCopyProtectionEnabled && !item.message.isCopyProtected(), let rootNode = item.controllerInteraction.chatControllerNode() {
|
||||
if self.textSelectionNode == nil, self.textClippingNode.supernode != nil, let item = self.arguments, !item.associatedData.isCopyProtectionEnabled && !item.message.isCopyProtected(), let rootNode = item.controllerInteraction.chatControllerNode() {
|
||||
let selectionColor: UIColor
|
||||
let knobColor: UIColor
|
||||
if item.message.effectivelyIncoming(item.context.account.peerId) {
|
||||
@ -1599,8 +1607,8 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
item.controllerInteraction.performTextSelectionAction(item.message.stableId, text, action)
|
||||
})
|
||||
self.textSelectionNode = textSelectionNode
|
||||
self.addSubnode(textSelectionNode)
|
||||
self.insertSubnode(textSelectionNode.highlightAreaNode, belowSubnode: self.textNode)
|
||||
self.textClippingNode.addSubnode(textSelectionNode)
|
||||
self.textClippingNode.insertSubnode(textSelectionNode.highlightAreaNode, belowSubnode: self.textNode)
|
||||
textSelectionNode.frame = self.textNode.frame
|
||||
textSelectionNode.highlightAreaNode.frame = self.textNode.frame
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user