diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 3c9ae80fa6..0ba11fbb30 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -2494,6 +2494,13 @@ Unused sets are archived when you add more."; "Message.PaymentSent" = "Payment: %@"; "Notification.PaymentSent" = "You have just successfully transferred {amount} to {name} for {title}"; +"Notification.PaymentSentNoTitle" = "You have just successfully transferred {amount} to {name}"; + +"Notification.PaymentSentRecurringInit" = "You have just successfully transferred {amount} to {name} for {title} and allowed future reccurrent payments"; +"Notification.PaymentSentRecurringInitNoTitle" = "You have just successfully transferred {amount} to {name} and allowed future reccurrent payments"; + +"Notification.PaymentSentRecurringUsed" = "You have just successfully transferred {amount} to {name} for {title} via recurrent payments"; +"Notification.PaymentSentRecurringUsedNoTitle" = "You have just successfully transferred {amount} to {name} via recurrent payments"; "Common.NotNow" = "Not Now"; @@ -7680,3 +7687,8 @@ Sorry for the inconvenience."; "WebApp.Settings" = "Settings"; "Bot.AccepRecurrentInfo" = "I accept [Terms of Service]() of **%1$@**"; + +"Chat.AudioTranscriptionRateAction" = "Rate Transcription"; +"Chat.AudioTranscriptionFeedbackTip" = "Thank you for your feedback."; +"Message.AudioTranscription.ErrorEmpty" = "No speech detected"; +"Message.AudioTranscription.ErrorTooLong" = "The audio is too long"; diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 4794fc8d64..856b949833 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -1018,31 +1018,36 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { let selectionPromise = self.selectedMessagesPromise let previousRecentlySearchedPeerOrder = Atomic<[EnginePeer.Id]>(value: []) - let fixedRecentlySearchedPeers = context.engine.peers.recentlySearchedPeers() - |> map { peers -> [RecentlySearchedPeer] in - var result: [RecentlySearchedPeer] = [] - let _ = previousRecentlySearchedPeerOrder.modify { current in - var updated: [EnginePeer.Id] = [] - for id in current { - inner: for peer in peers { - if peer.peer.peerId == id { - updated.append(id) - result.append(peer) - break inner + let fixedRecentlySearchedPeers: Signal<[RecentlySearchedPeer], NoError> + if case .chats = key { + fixedRecentlySearchedPeers = context.engine.peers.recentlySearchedPeers() + |> map { peers -> [RecentlySearchedPeer] in + var result: [RecentlySearchedPeer] = [] + let _ = previousRecentlySearchedPeerOrder.modify { current in + var updated: [EnginePeer.Id] = [] + for id in current { + inner: for peer in peers { + if peer.peer.peerId == id { + updated.append(id) + result.append(peer) + break inner + } } } - } - for peer in peers.reversed() { - if !updated.contains(peer.peer.peerId) { - updated.insert(peer.peer.peerId, at: 0) - result.insert(peer, at: 0) + for peer in peers.reversed() { + if !updated.contains(peer.peer.peerId) { + updated.insert(peer.peer.peerId, at: 0) + result.insert(peer, at: 0) + } } + return updated } - return updated + return result } - return result + } else { + fixedRecentlySearchedPeers = .single([]) } - + let downloadItems: Signal<(inProgressItems: [DownloadItem], doneItems: [RenderedRecentDownloadItem]), NoError> if key == .downloads { var firstTime = true @@ -1191,7 +1196,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { let accountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) |> take(1) let foundLocalPeers: Signal<(peers: [EngineRenderedPeer], unread: [EnginePeer.Id: (Int32, Bool)], recentlySearchedPeerIds: Set), NoError> - if let query = query { + if let query = query, case .chats = key { let fixedOrRemovedRecentlySearchedPeers = context.engine.peers.recentlySearchedPeers() |> map { peers -> [RecentlySearchedPeer] in let allIds = peers.map(\.peer.peerId) diff --git a/submodules/Display/Source/ActionSheetTextItem.swift b/submodules/Display/Source/ActionSheetTextItem.swift index 9d4fa2ced2..07d9d06d03 100644 --- a/submodules/Display/Source/ActionSheetTextItem.swift +++ b/submodules/Display/Source/ActionSheetTextItem.swift @@ -1,12 +1,15 @@ import Foundation import UIKit import AsyncDisplayKit +import Markdown public class ActionSheetTextItem: ActionSheetItem { public let title: String + public let parseMarkdown: Bool - public init(title: String) { + public init(title: String, parseMarkdown: Bool = true) { self.title = title + self.parseMarkdown = parseMarkdown } public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { @@ -63,8 +66,20 @@ public class ActionSheetTextNode: ActionSheetItemNode { self.item = item let defaultFont = Font.regular(floor(theme.baseFontSize * 13.0 / 17.0)) + let boldFont = Font.semibold(floor(theme.baseFontSize * 13.0 / 17.0)) + + if item.parseMarkdown { + let body = MarkdownAttributeSet(font: defaultFont, textColor: self.theme.secondaryTextColor) + let bold = MarkdownAttributeSet(font: boldFont, textColor: self.theme.secondaryTextColor) + let link = body + + self.label.attributedText = parseMarkdownIntoAttributedString(item.title, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in + return nil + })) + } else { + self.label.attributedText = NSAttributedString(string: item.title, font: defaultFont, textColor: self.theme.secondaryTextColor, paragraphAlignment: .center) + } - self.label.attributedText = NSAttributedString(string: item.title, font: defaultFont, textColor: self.theme.secondaryTextColor, paragraphAlignment: .center) self.accessibilityArea.accessibilityLabel = item.title } diff --git a/submodules/Display/Source/CAAnimationUtils.swift b/submodules/Display/Source/CAAnimationUtils.swift index 6f7ca8889b..c67008417d 100644 --- a/submodules/Display/Source/CAAnimationUtils.swift +++ b/submodules/Display/Source/CAAnimationUtils.swift @@ -52,6 +52,15 @@ public extension CAAnimation { } } +private func adjustFrameRate(animation: CAAnimation) { + if #available(iOS 15.0, *) { + let maxFps = Float(UIScreen.main.maximumFramesPerSecond) + if maxFps > 61.0 { + animation.preferredFrameRateRange = CAFrameRateRange(minimum: maxFps, maximum: maxFps, preferred: maxFps) + } + } +} + public extension CALayer { func makeAnimation(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, delay: Double = 0.0, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) -> CAAnimation { if timingFunction.hasPrefix(kCAMediaTimingFunctionCustomSpringPrefix) { @@ -84,9 +93,8 @@ public extension CALayer { animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor() animation.fillMode = .both } - if #available(iOS 15.0, *) { - animation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond)) - } + adjustFrameRate(animation: animation) + return animation } else if timingFunction == kCAMediaTimingFunctionSpring { let animation = makeSpringAnimation(keyPath) @@ -112,9 +120,7 @@ public extension CALayer { animation.fillMode = .both } - if #available(iOS 15.0, *) { - animation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond)) - } + adjustFrameRate(animation: animation) return animation } else { @@ -146,9 +152,7 @@ public extension CALayer { animation.fillMode = .both } - if #available(iOS 15.0, *) { - animation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond)) - } + adjustFrameRate(animation: animation) return animation } @@ -208,9 +212,7 @@ public extension CALayer { animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion) } - if #available(iOS 15.0, *) { - animation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond)) - } + adjustFrameRate(animation: animation) self.add(animation, forKey: keyPath) } @@ -241,9 +243,7 @@ public extension CALayer { animation.speed = speed * Float(animation.duration / duration) animation.isAdditive = additive - if #available(iOS 15.0, *) { - animation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond)) - } + adjustFrameRate(animation: animation) return animation } @@ -277,9 +277,7 @@ public extension CALayer { animation.speed = speed * Float(animation.duration / duration) animation.isAdditive = additive - if #available(iOS 15.0, *) { - animation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond)) - } + adjustFrameRate(animation: animation) self.add(animation, forKey: keyPath) } @@ -308,9 +306,7 @@ public extension CALayer { animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion) } - if #available(iOS 15.0, *) { - animation.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond)) - } + adjustFrameRate(animation: animation) self.add(animation, forKey: key) } diff --git a/submodules/Postbox/Sources/MessageHistoryReadStateTable.swift b/submodules/Postbox/Sources/MessageHistoryReadStateTable.swift index 44bd024e5f..ac3a903468 100644 --- a/submodules/Postbox/Sources/MessageHistoryReadStateTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryReadStateTable.swift @@ -300,7 +300,7 @@ final class MessageHistoryReadStateTable: Table { return (nil, false) } - func applyIncomingMaxReadIndex(_ messageIndex: MessageIndex, topMessageIndex: MessageIndex?, incomingStatsInRange: (MessageIndex, MessageIndex) -> (count: Int, holes: Bool, readMesageIds: [MessageId])) -> (CombinedPeerReadState?, Bool, [MessageId]) { + func applyIncomingMaxReadIndex(_ messageIndex: MessageIndex, topMessageIndex: MessageIndex?, incomingStatsInRange: (MessageIndex, MessageIndex) -> (count: Int, holes: Bool, readMessageIds: [MessageId])) -> (CombinedPeerReadState?, Bool, [MessageId]) { if let states = self.get(messageIndex.id.peerId), let state = states.namespaces[messageIndex.id.namespace] { if traceReadStates { print("[ReadStateTable] applyIncomingMaxReadIndex peerId: \(messageIndex.id.peerId), maxReadIndex: \(messageIndex) (before: \(states.namespaces))") @@ -380,7 +380,7 @@ final class MessageHistoryReadStateTable: Table { return (nil, false, []) } - func applyInteractiveMaxReadIndex(postbox: PostboxImpl, messageIndex: MessageIndex, incomingStatsInRange: (MessageId.Namespace, MessageId.Id, MessageId.Id) -> (count: Int, holes: Bool), incomingIndexStatsInRange: (MessageIndex, MessageIndex) -> (count: Int, holes: Bool, readMesageIds: [MessageId]), topMessageId: (MessageId.Id, Bool)?, topMessageIndexByNamespace: (MessageId.Namespace) -> MessageIndex?) -> (combinedState: CombinedPeerReadState?, ApplyInteractiveMaxReadIdResult, readMesageIds: [MessageId]) { + func applyInteractiveMaxReadIndex(postbox: PostboxImpl, messageIndex: MessageIndex, incomingStatsInRange: (MessageId.Namespace, MessageId.Id, MessageId.Id) -> (count: Int, holes: Bool), incomingIndexStatsInRange: (MessageIndex, MessageIndex) -> (count: Int, holes: Bool, readMessageIds: [MessageId]), topMessageId: (MessageId.Id, Bool)?, topMessageIndexByNamespace: (MessageId.Namespace) -> MessageIndex?) -> (combinedState: CombinedPeerReadState?, ApplyInteractiveMaxReadIdResult, readMessageIds: [MessageId]) { if let states = self.get(messageIndex.id.peerId) { if let state = states.namespaces[messageIndex.id.namespace] { switch state { diff --git a/submodules/Postbox/Sources/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index c7694c699e..fb658e425a 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -1118,7 +1118,7 @@ public final class MessageHistoryView { self.maxReadIndex = nil } case let .external(input): - if let maxReadMesageId = input.maxReadIncomingMessageId { + if let maxReadMessageId = input.maxReadIncomingMessageId { var maxIndex: MessageIndex? let hasUnread = true @@ -1128,15 +1128,15 @@ public final class MessageHistoryView { peerIds.insert(entry.index.id.peerId) } for peerId in peerIds { - if peerId != maxReadMesageId.peerId { + if peerId != maxReadMessageId.peerId { continue } - let namespace = maxReadMesageId.namespace + let namespace = maxReadMessageId.namespace var maxNamespaceIndex: MessageIndex? var index = entries.count - 1 for entry in entries.reversed() { - if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace && entry.index.id <= maxReadMesageId { + if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace && entry.index.id <= maxReadMessageId { maxNamespaceIndex = entry.index break } diff --git a/submodules/ShareController/BUILD b/submodules/ShareController/BUILD index f6222b6c6a..7ff8806c79 100644 --- a/submodules/ShareController/BUILD +++ b/submodules/ShareController/BUILD @@ -34,6 +34,7 @@ swift_library( "//submodules/ContextUI:ContextUI", "//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", + "//submodules/TelegramUniversalVideoContent:TelegramUniversalVideoContent", ], visibility = [ "//visibility:public", diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index 9055ca93b5..addc248941 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -424,8 +424,21 @@ public final class ShareController: ViewController { if case .saveToCameraRoll = preferredAction { self.actionIsMediaSaving = true self.defaultAction = ShareControllerAction(title: self.presentationData.strings.Preview_SaveToCameraRoll, action: { [weak self] in - self?.saveToCameraRoll(messages: messages) - self?.actionCompleted?() + guard let strongSelf = self else { + return + } + + let actionCompleted = strongSelf.actionCompleted + strongSelf.saveToCameraRoll(messages: messages, completion: { + actionCompleted?() + + guard let strongSelf = self else { + return + } + strongSelf.controllerNode.animateOut(shared: false, completion: { + self?.presentingViewController?.dismiss(animated: false, completion: nil) + }) + }) }) } else if let message = messages.first { let groupingKey: Int64? = message.groupingKey @@ -913,7 +926,7 @@ public final class ShareController: ViewController { self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) } - private func saveToCameraRoll(messages: [Message]) { + private func saveToCameraRoll(messages: [Message], completion: @escaping () -> Void) { let postbox = self.currentAccount.postbox let signals: [Signal] = messages.compactMap { message -> Signal? in if let media = message.media.first { @@ -938,7 +951,7 @@ public final class ShareController: ViewController { total /= Float(values.count) return total } - self.controllerNode.transitionToProgressWithValue(signal: total) + self.controllerNode.transitionToProgressWithValue(signal: total, completion: completion) } } @@ -950,7 +963,7 @@ public final class ShareController: ViewController { } else { context = self.sharedContext.makeTempAccountContext(account: self.currentAccount) } - self.controllerNode.transitionToProgressWithValue(signal: SaveToCameraRoll.saveToCameraRoll(context: context, postbox: context.account.postbox, mediaReference: .standalone(media: media)) |> map(Optional.init), dismissImmediately: true) + self.controllerNode.transitionToProgressWithValue(signal: SaveToCameraRoll.saveToCameraRoll(context: context, postbox: context.account.postbox, mediaReference: .standalone(media: media)) |> map(Optional.init), dismissImmediately: true, completion: {}) } private func saveToCameraRoll(mediaReference: AnyMediaReference) { @@ -960,7 +973,7 @@ public final class ShareController: ViewController { } else { context = self.sharedContext.makeTempAccountContext(account: self.currentAccount) } - self.controllerNode.transitionToProgressWithValue(signal: SaveToCameraRoll.saveToCameraRoll(context: context, postbox: context.account.postbox, mediaReference: mediaReference) |> map(Optional.init), dismissImmediately: true) + self.controllerNode.transitionToProgressWithValue(signal: SaveToCameraRoll.saveToCameraRoll(context: context, postbox: context.account.postbox, mediaReference: mediaReference) |> map(Optional.init), dismissImmediately: true, completion: {}) } private func switchToAccount(account: Account, animateIn: Bool) { diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index fd5f9b8214..0130b9cb35 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -696,7 +696,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate if fromForeignApp, case let .preparing(long) = status, !transitioned { transitioned = true if long { - strongSelf.transitionToContentNode(ShareProlongedLoadingContainerNode(theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, forceNativeAppearance: true), fastOut: true) + strongSelf.transitionToContentNode(ShareProlongedLoadingContainerNode(theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, forceNativeAppearance: true, account: strongSelf.context?.account, sharedContext: strongSelf.sharedContext), fastOut: true) } else { strongSelf.transitionToContentNode(ShareLoadingContainerNode(theme: strongSelf.presentationData.theme, forceNativeAppearance: true), fastOut: true) } @@ -1006,7 +1006,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate transition.updateAlpha(node: self.actionSeparatorNode, alpha: 0.0) transition.updateAlpha(node: self.actionsBackgroundNode, alpha: 0.0) - self.transitionToContentNode(ShareProlongedLoadingContainerNode(theme: self.presentationData.theme, strings: self.presentationData.strings, forceNativeAppearance: true), fastOut: true) + self.transitionToContentNode(ShareProlongedLoadingContainerNode(theme: self.presentationData.theme, strings: self.presentationData.strings, forceNativeAppearance: true, account: self.context?.account, sharedContext: self.sharedContext), fastOut: true) let timestamp = CACurrentMediaTime() self.shareDisposable.set(signal.start(completed: { [weak self] in let minDelay = 0.6 @@ -1021,7 +1021,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate })) } - func transitionToProgressWithValue(signal: Signal, dismissImmediately: Bool = false) { + func transitionToProgressWithValue(signal: Signal, dismissImmediately: Bool = false, completion: @escaping () -> Void) { self.inputFieldNode.deactivateInput() if dismissImmediately { @@ -1034,6 +1034,8 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate if let strongSelf = self { strongSelf.dismiss?(true) } + + completion() })) } else { let transition = ContainedViewLayoutTransition.animated(duration: 0.12, curve: .easeInOut) @@ -1066,6 +1068,8 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate contentNode.state = .progress(status) } }, completed: { [weak self] in + completion() + guard let strongSelf = self, let contentNode = strongSelf.contentNode as? ShareLoadingContainer else { return } diff --git a/submodules/ShareController/Sources/ShareControllerRecentPeersGridItem.swift b/submodules/ShareController/Sources/ShareControllerRecentPeersGridItem.swift index 993825886e..f96f74110e 100644 --- a/submodules/ShareController/Sources/ShareControllerRecentPeersGridItem.swift +++ b/submodules/ShareController/Sources/ShareControllerRecentPeersGridItem.swift @@ -83,7 +83,7 @@ final class ShareControllerRecentPeersGridItemNode: GridItemNode { let bounds = self.bounds - self.peersNode?.frame = CGRect(origin: CGPoint(), size: bounds.size) + self.peersNode?.frame = CGRect(origin: CGPoint(x: -8.0, y: 0.0), size: CGSize(width: bounds.width + 8.0, height: bounds.height)) self.peersNode?.updateLayout(size: bounds.size, leftInset: 0.0, rightInset: 0.0) } } diff --git a/submodules/ShareController/Sources/ShareLoadingContainerNode.swift b/submodules/ShareController/Sources/ShareLoadingContainerNode.swift index 7e915c21a0..98655ec088 100644 --- a/submodules/ShareController/Sources/ShareLoadingContainerNode.swift +++ b/submodules/ShareController/Sources/ShareLoadingContainerNode.swift @@ -9,6 +9,10 @@ import ActivityIndicator import RadialStatusNode import AnimatedStickerNode import TelegramAnimatedStickerNode +import AppBundle +import TelegramUniversalVideoContent +import TelegramCore +import AccountContext public enum ShareLoadingState { case preparing @@ -115,6 +119,8 @@ public final class ShareProlongedLoadingContainerNode: ASDisplayNode, ShareConte private var startTimestamp: Double? + private var videoNode: UniversalVideoNode? + public var state: ShareLoadingState = .preparing { didSet { switch self.state { @@ -202,7 +208,7 @@ public final class ShareProlongedLoadingContainerNode: ASDisplayNode, ShareConte return self.elapsedTime + 3.0 + 0.15 } - public init(theme: PresentationTheme, strings: PresentationStrings, forceNativeAppearance: Bool) { + public init(theme: PresentationTheme, strings: PresentationStrings, forceNativeAppearance: Bool, account: Account?, sharedContext: SharedAccountContext) { self.theme = theme self.strings = strings @@ -242,6 +248,23 @@ public final class ShareProlongedLoadingContainerNode: ASDisplayNode, ShareConte strongSelf.elapsedTime = status.duration - status.timestamp } })) + + if let account = account, let path = getAppBundle().path(forResource: "BlankVideo", ofType: "m4v"), let size = fileSize(path) { + let decoration = ChatBubbleVideoDecoration(corners: ImageCorners(), nativeSize: CGSize(width: 100.0, height: 100.0), contentMode: .aspectFit, backgroundColor: .black) + + let dummyFile = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [])]) + + let videoContent = NativeVideoContent(id: .message(1, MediaId(namespace: 0, id: 1)), fileReference: .standalone(media: dummyFile), streamVideo: .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .black) + + let videoNode = UniversalVideoNode(postbox: account.postbox, audioSession: sharedContext.mediaManager.audioSession, manager: sharedContext.mediaManager.universalVideoManager, decoration: decoration, content: videoContent, priority: .embedded) + videoNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 2.0, height: 2.0)) + videoNode.alpha = 0.01 + self.videoNode = videoNode + + self.addSubnode(videoNode) + videoNode.canAttachContent = true + videoNode.play() + } } deinit { diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 2906cb9b13..5bc024067b 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -808,7 +808,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1135492588] = { return Api.Update.parse_updateStickerSets($0) } dict[196268545] = { return Api.Update.parse_updateStickerSetsOrder($0) } dict[-2112423005] = { return Api.Update.parse_updateTheme($0) } - dict[-2006880112] = { return Api.Update.parse_updateTranscribeAudio($0) } + dict[8703322] = { return Api.Update.parse_updateTranscribedAudio($0) } dict[-1007549728] = { return Api.Update.parse_updateUserName($0) } dict[88680979] = { return Api.Update.parse_updateUserPhone($0) } dict[-232290676] = { return Api.Update.parse_updateUserPhoto($0) } diff --git a/submodules/TelegramApi/Sources/Api19.swift b/submodules/TelegramApi/Sources/Api19.swift index 54b86471e6..7f1724a2b6 100644 --- a/submodules/TelegramApi/Sources/Api19.swift +++ b/submodules/TelegramApi/Sources/Api19.swift @@ -599,7 +599,7 @@ public extension Api { case updateStickerSets case updateStickerSetsOrder(flags: Int32, order: [Int64]) case updateTheme(theme: Api.Theme) - case updateTranscribeAudio(flags: Int32, transcriptionId: Int64, text: String) + case updateTranscribedAudio(flags: Int32, peer: Api.Peer, msgId: Int32, transcriptionId: Int64, text: String) case updateUserName(userId: Int64, firstName: String, lastName: String, username: String) case updateUserPhone(userId: Int64, phone: String) case updateUserPhoto(userId: Int64, date: Int32, photo: Api.UserProfilePhoto, previous: Api.Bool) @@ -1424,11 +1424,13 @@ public extension Api { } theme.serialize(buffer, true) break - case .updateTranscribeAudio(let flags, let transcriptionId, let text): + case .updateTranscribedAudio(let flags, let peer, let msgId, let transcriptionId, let text): if boxed { - buffer.appendInt32(-2006880112) + buffer.appendInt32(8703322) } serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) serializeInt64(transcriptionId, buffer: buffer, boxed: false) serializeString(text, buffer: buffer, boxed: false) break @@ -1676,8 +1678,8 @@ public extension Api { return ("updateStickerSetsOrder", [("flags", String(describing: flags)), ("order", String(describing: order))]) case .updateTheme(let theme): return ("updateTheme", [("theme", String(describing: theme))]) - case .updateTranscribeAudio(let flags, let transcriptionId, let text): - return ("updateTranscribeAudio", [("flags", String(describing: flags)), ("transcriptionId", String(describing: transcriptionId)), ("text", String(describing: text))]) + case .updateTranscribedAudio(let flags, let peer, let msgId, let transcriptionId, let text): + return ("updateTranscribedAudio", [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("transcriptionId", String(describing: transcriptionId)), ("text", String(describing: text))]) case .updateUserName(let userId, let firstName, let lastName, let username): return ("updateUserName", [("userId", String(describing: userId)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName)), ("username", String(describing: username))]) case .updateUserPhone(let userId, let phone): @@ -3336,18 +3338,26 @@ public extension Api { return nil } } - public static func parse_updateTranscribeAudio(_ reader: BufferReader) -> Update? { + public static func parse_updateTranscribedAudio(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() + var _5: String? + _5 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateTranscribeAudio(flags: _1!, transcriptionId: _2!, text: _3!) + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateTranscribedAudio(flags: _1!, peer: _2!, msgId: _3!, transcriptionId: _4!, text: _5!) } else { return nil diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index 58930df3ef..859db18a16 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -113,7 +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) + case UpdateAudioTranscription(messageId: MessageId, id: Int64, isPending: Bool, text: String) } struct HoleFromPreviousState { @@ -509,8 +509,8 @@ 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 updateAudioTranscription(messageId: MessageId, id: Int64, isPending: Bool, text: String) { + self.addOperation(.UpdateAudioTranscription(messageId: messageId, id: id, isPending: isPending, text: text)) } mutating func addDismissedWebView(queryId: Int64) { diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index e8b2b58f6a..cd4492c8cb 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -40,8 +40,10 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .phoneCall(callId: callId, discardReason: discardReason, duration: duration, isVideo: isVideo)) case .messageActionEmpty: return nil - case let .messageActionPaymentSent(_, currency, totalAmount, invoiceSlug): - return TelegramMediaAction(action: .paymentSent(currency: currency, totalAmount: totalAmount, invoiceSlug: invoiceSlug)) + case let .messageActionPaymentSent(flags, currency, totalAmount, invoiceSlug): + let isRecurringInit = (flags & (1 << 2)) != 0 + let isRecurringUsed = (flags & (1 << 3)) != 0 + return TelegramMediaAction(action: .paymentSent(currency: currency, totalAmount: totalAmount, invoiceSlug: invoiceSlug, isRecurringInit: isRecurringInit, isRecurringUsed: isRecurringUsed)) case .messageActionPaymentSentMe: return nil case .messageActionScreenshotTaken: diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 04c75665dd..24212dd5e2 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -1101,9 +1101,9 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo updatedState.updateMedia(webpage.webpageId, media: webpage) } } - case let .updateTranscribeAudio(flags, transcriptionId, text): - let isFinal = (flags & (1 << 0)) != 0 - updatedState.updateAudioTranscription(id: transcriptionId, isPending: !isFinal, text: text) + case let .updateTranscribedAudio(flags, peer, msgId, transcriptionId, text): + let isPending = (flags & (1 << 0)) != 0 + updatedState.updateAudioTranscription(messageId: MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: msgId), id: transcriptionId, isPending: isPending, text: text) case let .updateNotifySettings(apiPeer, apiNotificationSettings): switch apiPeer { case let .notifyPeer(peer): @@ -2403,8 +2403,7 @@ func replayFinalState( auxiliaryMethods: AccountAuxiliaryMethods, finalState: AccountFinalState, removePossiblyDeliveredMessagesUniqueIds: [Int64: PeerId], - ignoreDate: Bool, - audioTranscriptionManager: Atomic? + ignoreDate: Bool ) -> AccountReplayedFinalState? { let verified = verifyTransaction(transaction, finalState: finalState.state) if !verified { @@ -3344,48 +3343,42 @@ 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 - )) - }) + case let .UpdateAudioTranscription(messageId, id, isPending, text): + 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, error: nil) + found = true + break loop + } + } + if !found { + attributes.append(AudioTranscriptionMessageAttribute(id: id, text: text, isPending: isPending, didRate: false, error: nil)) + } + + return .update(StoreMessage( + id: currentMessage.id, + globallyUniqueId: currentMessage.globallyUniqueId, + groupingKey: currentMessage.groupingKey, + threadId: currentMessage.threadId, + timestamp: currentMessage.timestamp, + flags: StoreMessageFlags(currentMessage.flags), + tags: currentMessage.tags, + globalTags: currentMessage.globalTags, + localTags: currentMessage.localTags, + forwardInfo: storeForwardInfo, + authorId: currentMessage.author?.id, + text: currentMessage.text, + attributes: attributes, + media: currentMessage.media + )) + }) } } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManager.swift b/submodules/TelegramCore/Sources/State/AccountStateManager.swift index 680b32ac81..f42199846e 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManager.swift @@ -64,8 +64,6 @@ public final class AccountStateManager { let auxiliaryMethods: AccountAuxiliaryMethods var transformOutgoingMessageMedia: TransformOutgoingMessageMedia? - let audioTranscriptionManager = Atomic(value: AudioTranscriptionManager()) - private var updateService: UpdateMessageService? private let updateServiceDisposable = MetaDisposable() @@ -427,7 +425,6 @@ public final class AccountStateManager { let mediaBox = postbox.mediaBox let accountPeerId = self.accountPeerId let auxiliaryMethods = self.auxiliaryMethods - let audioTranscriptionManager = self.audioTranscriptionManager let signal = postbox.stateView() |> mapToSignal { view -> Signal in if let state = view.state as? AuthorizedAccountState { @@ -479,7 +476,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, audioTranscriptionManager: audioTranscriptionManager) + 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 deltaTime = CFAbsoluteTimeGetCurrent() - startTime if deltaTime > 1.0 { Logger.shared.log("State", "replayFinalState took \(deltaTime)s") @@ -578,7 +575,6 @@ 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 @@ -598,7 +594,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, audioTranscriptionManager: audioTranscriptionManager) + 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 deltaTime = CFAbsoluteTimeGetCurrent() - startTime if deltaTime > 1.0 { Logger.shared.log("State", "replayFinalState took \(deltaTime)s") @@ -831,11 +827,10 @@ 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, audioTranscriptionManager: audioTranscriptionManager) + 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 deltaTime = CFAbsoluteTimeGetCurrent() - startTime if deltaTime > 1.0 { Logger.shared.log("State", "replayFinalState took \(deltaTime)s") @@ -877,11 +872,10 @@ 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, audioTranscriptionManager: audioTranscriptionManager) + 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 deltaTime = CFAbsoluteTimeGetCurrent() - startTime if deltaTime > 1.0 { Logger.shared.log("State", "replayFinalState took \(deltaTime)s") @@ -903,7 +897,6 @@ public final class AccountStateManager { let mediaBox = postbox.mediaBox let accountPeerId = self.accountPeerId let auxiliaryMethods = self.auxiliaryMethods - let audioTranscriptionManager = self.audioTranscriptionManager let signal = postbox.stateView() |> mapToSignal { view -> Signal in @@ -966,8 +959,7 @@ public final class AccountStateManager { auxiliaryMethods: auxiliaryMethods, finalState: finalState, removePossiblyDeliveredMessagesUniqueIds: removePossiblyDeliveredMessagesUniqueIds, - ignoreDate: true, - audioTranscriptionManager: audioTranscriptionManager + ignoreDate: true ) let deltaTime = CFAbsoluteTimeGetCurrent() - startTime if deltaTime > 1.0 { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AudioTranscriptionMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AudioTranscriptionMessageAttribute.swift index 31dcf80926..beaf799980 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AudioTranscriptionMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AudioTranscriptionMessageAttribute.swift @@ -1,20 +1,27 @@ import Postbox public class AudioTranscriptionMessageAttribute: MessageAttribute, Equatable { + public enum TranscriptionError: Int32, Error { + case generic = 0 + case tooLong = 1 + } + public let id: Int64 public let text: String public let isPending: Bool public let didRate: Bool + public let error: TranscriptionError? public var associatedPeerIds: [PeerId] { return [] } - public init(id: Int64, text: String, isPending: Bool, didRate: Bool) { + public init(id: Int64, text: String, isPending: Bool, didRate: Bool, error: TranscriptionError?) { self.id = id self.text = text self.isPending = isPending self.didRate = didRate + self.error = error } required public init(decoder: PostboxDecoder) { @@ -22,6 +29,11 @@ public class AudioTranscriptionMessageAttribute: MessageAttribute, Equatable { self.text = decoder.decodeStringForKey("text", orElse: "") self.isPending = decoder.decodeBoolForKey("isPending", orElse: false) self.didRate = decoder.decodeBoolForKey("didRate", orElse: false) + if let errorValue = decoder.decodeOptionalInt32ForKey("error") { + self.error = TranscriptionError(rawValue: errorValue) + } else { + self.error = nil + } } public func encode(_ encoder: PostboxEncoder) { @@ -29,6 +41,11 @@ public class AudioTranscriptionMessageAttribute: MessageAttribute, Equatable { encoder.encodeString(self.text, forKey: "text") encoder.encodeBool(self.isPending, forKey: "isPending") encoder.encodeBool(self.didRate, forKey: "didRate") + if let error = self.error { + encoder.encodeInt32(error.rawValue, forKey: "error") + } else { + encoder.encodeNil(forKey: "error") + } } public static func ==(lhs: AudioTranscriptionMessageAttribute, rhs: AudioTranscriptionMessageAttribute) -> Bool { @@ -44,14 +61,17 @@ public class AudioTranscriptionMessageAttribute: MessageAttribute, Equatable { if lhs.didRate != rhs.didRate { return false } + if lhs.error != rhs.error { + 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) + return AudioTranscriptionMessageAttribute(id: self.id, text: self.text, isPending: self.isPending, didRate: self.didRate || other.didRate, error: self.error) } func withDidRate() -> AudioTranscriptionMessageAttribute { - return AudioTranscriptionMessageAttribute(id: self.id, text: self.text, isPending: self.isPending, didRate: true) + return AudioTranscriptionMessageAttribute(id: self.id, text: self.text, isPending: self.isPending, didRate: true, error: self.error) } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index 8038226c87..48bf5de03f 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -39,7 +39,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case messageAutoremoveTimeoutUpdated(Int32) case gameScore(gameId: Int64, score: Int32) case phoneCall(callId: Int64, discardReason: PhoneCallDiscardReason?, duration: Int32?, isVideo: Bool) - case paymentSent(currency: String, totalAmount: Int64, invoiceSlug: String?) + case paymentSent(currency: String, totalAmount: Int64, invoiceSlug: String?, isRecurringInit: Bool, isRecurringUsed: Bool) case customText(text: String, entities: [MessageTextEntity]) case botDomainAccessGranted(domain: String) case botSentSecureValues(types: [SentSecureValueType]) @@ -88,7 +88,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { } self = .phoneCall(callId: decoder.decodeInt64ForKey("i", orElse: 0), discardReason: discardReason, duration: decoder.decodeInt32ForKey("d", orElse: 0), isVideo: decoder.decodeInt32ForKey("vc", orElse: 0) != 0) case 15: - self = .paymentSent(currency: decoder.decodeStringForKey("currency", orElse: ""), totalAmount: decoder.decodeInt64ForKey("ta", orElse: 0), invoiceSlug: decoder.decodeOptionalStringForKey("invoiceSlug")) + self = .paymentSent(currency: decoder.decodeStringForKey("currency", orElse: ""), totalAmount: decoder.decodeInt64ForKey("ta", orElse: 0), invoiceSlug: decoder.decodeOptionalStringForKey("invoiceSlug"), isRecurringInit: decoder.decodeBoolForKey("isRecurringInit", orElse: false), isRecurringUsed: decoder.decodeBoolForKey("isRecurringUsed", orElse: false)) case 16: self = .customText(text: decoder.decodeStringForKey("text", orElse: ""), entities: decoder.decodeObjectArrayWithDecoderForKey("ent")) case 17: @@ -172,7 +172,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { encoder.encodeInt32(13, forKey: "_rawValue") encoder.encodeInt64(gameId, forKey: "i") encoder.encodeInt32(score, forKey: "s") - case let .paymentSent(currency, totalAmount, invoiceSlug): + case let .paymentSent(currency, totalAmount, invoiceSlug, isRecurringInit, isRecurringUsed): encoder.encodeInt32(15, forKey: "_rawValue") encoder.encodeString(currency, forKey: "currency") encoder.encodeInt64(totalAmount, forKey: "ta") @@ -181,6 +181,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { } else { encoder.encodeNil(forKey: "invoiceSlug") } + encoder.encodeBool(isRecurringInit, forKey: "isRecurringInit") + encoder.encodeBool(isRecurringUsed, forKey: "isRecurringUsed") case let .phoneCall(callId, discardReason, duration, isVideo): encoder.encodeInt32(14, forKey: "_rawValue") encoder.encodeInt64(callId, forKey: "i") diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift index c45a9a47ee..b2ccc018eb 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift @@ -434,14 +434,14 @@ private class ReplyThreadHistoryContextImpl { return .single(nil) } |> afterNext { result in - guard let (incomingMesageId, count) = result else { + guard let (incomingMessageId, count) = result else { return } Queue.mainQueue().async { guard let strongSelf = self else { return } - strongSelf.maxReadIncomingMessageIdValue = incomingMesageId + strongSelf.maxReadIncomingMessageIdValue = incomingMessageId strongSelf.unreadCountValue = count } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 316fb5799b..dc8c8ac356 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -329,16 +329,16 @@ public extension TelegramEngine { } public func transcribeAudio(messageId: MessageId) -> Signal { - return _internal_transcribeAudio(postbox: self.account.postbox, network: self.account.network, audioTranscriptionManager: self.account.stateManager.audioTranscriptionManager, messageId: messageId) + return _internal_transcribeAudio(postbox: self.account.postbox, network: self.account.network, messageId: messageId) } - public func storeLocallyTranscribedAudio(messageId: MessageId, text: String, isFinal: Bool) -> Signal { + public func storeLocallyTranscribedAudio(messageId: MessageId, text: String, isFinal: Bool, error: AudioTranscriptionMessageAttribute.TranscriptionError?) -> Signal { return self.account.postbox.transaction { transaction -> Void in transaction.updateMessage(messageId, update: { currentMessage in let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) var attributes = currentMessage.attributes.filter { !($0 is AudioTranscriptionMessageAttribute) } - attributes.append(AudioTranscriptionMessageAttribute(id: 0, text: text, isPending: !isFinal, didRate: false)) + attributes.append(AudioTranscriptionMessageAttribute(id: 0, text: text, isPending: !isFinal, didRate: false, error: error)) return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift index e683ef1cf0..74ed81476b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift @@ -34,22 +34,7 @@ public enum EngineAudioTranscriptionResult { case error } -class AudioTranscriptionManager { - private var pendingMapping: [Int64: MessageId] = [:] - - init() { - } - - func addPendingMapping(transcriptionId: Int64, messageId: MessageId) { - self.pendingMapping[transcriptionId] = messageId - } - - func getPendingMapping(transcriptionId: Int64) -> MessageId? { - return self.pendingMapping[transcriptionId] - } -} - -func _internal_transcribeAudio(postbox: Postbox, network: Network, audioTranscriptionManager: Atomic, messageId: MessageId) -> Signal { +func _internal_transcribeAudio(postbox: Postbox, network: Network, messageId: MessageId) -> Signal { return postbox.transaction { transaction -> Api.InputPeer? in return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) } @@ -58,28 +43,31 @@ func _internal_transcribeAudio(postbox: Postbox, network: Network, audioTranscri return .single(.error) } return network.request(Api.functions.messages.transcribeAudio(peer: inputPeer, msgId: messageId.id)) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) + |> map { result -> Result in + return .success(result) + } + |> `catch` { error -> Signal, NoError> in + let mappedError: AudioTranscriptionMessageAttribute.TranscriptionError + if error.errorDescription == "MSG_VOICE_TOO_LONG" { + mappedError = .tooLong + } else { + mappedError = .generic + } + return .single(.failure(mappedError)) } |> mapToSignal { result -> Signal in return postbox.transaction { transaction -> EngineAudioTranscriptionResult in let updatedAttribute: AudioTranscriptionMessageAttribute - if let result = result { - switch result { + switch result { + case let .success(transcribedAudio): + switch transcribedAudio { case let .transcribedAudio(flags, transcriptionId, text): let isPending = (flags & (1 << 0)) != 0 - if isPending { - audioTranscriptionManager.with { audioTranscriptionManager in - audioTranscriptionManager.addPendingMapping(transcriptionId: transcriptionId, messageId: messageId) - } - } - - updatedAttribute = AudioTranscriptionMessageAttribute(id: transcriptionId, text: text, isPending: isPending, didRate: false) + updatedAttribute = AudioTranscriptionMessageAttribute(id: transcriptionId, text: text, isPending: isPending, didRate: false, error: nil) } - } else { - updatedAttribute = AudioTranscriptionMessageAttribute(id: 0, text: "", isPending: false, didRate: false) + case let .failure(error): + updatedAttribute = AudioTranscriptionMessageAttribute(id: 0, text: "", isPending: false, didRate: false, error: error) } transaction.updateMessage(messageId, update: { currentMessage in @@ -91,7 +79,7 @@ func _internal_transcribeAudio(postbox: Postbox, network: Network, audioTranscri 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 { + if updatedAttribute.error == nil { return .success } else { return .error diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift index 06ff9acc56..4518080812 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/BotPaymentForm.swift @@ -482,7 +482,7 @@ func _internal_sendBotPaymentForm(account: Account, formId: Int64, source: BotPa switch source { case let .slug(slug): for media in message.media { - if let action = media as? TelegramMediaAction, case let .paymentSent(_, _, invoiceSlug?) = action.action, invoiceSlug == slug { + if let action = media as? TelegramMediaAction, case let .paymentSent(_, _, invoiceSlug?, _, _) = action.action, invoiceSlug == slug { if case let .Id(id) = message.id { receiptMessageId = id } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift index ac45fba6a9..7c73fc2a01 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift @@ -71,6 +71,12 @@ func _internal_joinChannel(account: Account, peerId: PeerId, hash: String?) -> S } } } + + if let channel = transaction.getPeer(peerId) as? TelegramChannel, case .broadcast = channel.info { + let notificationSettings = transaction.getPeerNotificationSettings(peerId) as? TelegramPeerNotificationSettings ?? TelegramPeerNotificationSettings.defaultSettings + transaction.updateCurrentPeerNotificationSettings([peerId: notificationSettings.withUpdatedMuteState(.muted(until: Int32.max))]) + } + return RenderedChannelParticipant(participant: updatedParticipant, peer: peer, peers: peers, presences: presences) } |> castError(JoinChannelError.self) diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 1603d1fc33..6297ea853e 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -437,7 +437,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, var argumentAttributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]) argumentAttributes[1] = MarkdownAttributeSet(font: titleBoldFont, textColor: primaryTextColor, additionalAttributes: [:]) attributedString = addAttributesToStringWithRanges(formatWithArgumentRanges(baseString, ranges, [authorName, gameTitle ?? ""]), body: bodyAttributes, argumentAttributes: argumentAttributes) - case let .paymentSent(currency, totalAmount, _): + case let .paymentSent(currency, totalAmount, _, isRecurringInit, isRecurringUsed): var invoiceMessage: EngineMessage? for attribute in message.attributes { if let attribute = attribute as? ReplyMessageAttribute, let message = message.associatedMessages[attribute.messageId] { @@ -454,34 +454,53 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } } - if let invoiceTitle = invoiceTitle { - let botString: String - if let peer = messageMainPeer(message) { - botString = peer.compactDisplayTitle + let patternString: String + if isRecurringInit { + if let _ = invoiceTitle { + patternString = strings.Notification_PaymentSentRecurringInit } else { - botString = "" + patternString = strings.Notification_PaymentSentRecurringInitNoTitle } - let mutableString = NSMutableAttributedString() - mutableString.append(NSAttributedString(string: strings.Notification_PaymentSent, font: titleFont, textColor: primaryTextColor)) - - var range = NSRange(location: NSNotFound, length: 0) - - range = (mutableString.string as NSString).range(of: "{amount}") - if range.location != NSNotFound { - mutableString.replaceCharacters(in: range, with: NSAttributedString(string: formatCurrencyAmount(totalAmount, currency: currency), font: titleBoldFont, textColor: primaryTextColor)) + } else if isRecurringUsed { + if let _ = invoiceTitle { + patternString = strings.Notification_PaymentSentRecurringUsed + } else { + patternString = strings.Notification_PaymentSentRecurringUsedNoTitle } - range = (mutableString.string as NSString).range(of: "{name}") - if range.location != NSNotFound { - mutableString.replaceCharacters(in: range, with: NSAttributedString(string: botString, font: titleBoldFont, textColor: primaryTextColor)) + } else { + if let _ = invoiceTitle { + patternString = strings.Notification_PaymentSent + } else { + patternString = strings.Notification_PaymentSentNoTitle } + } + + let botString: String + if let peer = messageMainPeer(message) { + botString = peer.compactDisplayTitle + } else { + botString = "" + } + let mutableString = NSMutableAttributedString() + mutableString.append(NSAttributedString(string: patternString, font: titleFont, textColor: primaryTextColor)) + + var range = NSRange(location: NSNotFound, length: 0) + + range = (mutableString.string as NSString).range(of: "{amount}") + if range.location != NSNotFound { + mutableString.replaceCharacters(in: range, with: NSAttributedString(string: formatCurrencyAmount(totalAmount, currency: currency), font: titleBoldFont, textColor: primaryTextColor)) + } + range = (mutableString.string as NSString).range(of: "{name}") + if range.location != NSNotFound { + mutableString.replaceCharacters(in: range, with: NSAttributedString(string: botString, font: titleBoldFont, textColor: primaryTextColor)) + } + if let invoiceTitle = invoiceTitle { range = (mutableString.string as NSString).range(of: "{title}") if range.location != NSNotFound { mutableString.replaceCharacters(in: range, with: NSAttributedString(string: invoiceTitle, font: titleFont, textColor: primaryTextColor)) } - attributedString = mutableString - } else { - attributedString = NSAttributedString(string: strings.Message_PaymentSent(formatCurrencyAmount(totalAmount, currency: currency)).string, font: titleFont, textColor: primaryTextColor) } + attributedString = mutableString case let .phoneCall(_, discardReason, _, _): var titleString: String let incoming: Bool diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 77364a616b..471987ee10 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -715,8 +715,8 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState let _ = context.engine.messages.rateAudioTranscription(messageId: message.id, id: audioTranscription.id, isGood: value).start() - //TODO:localize - let content: UndoOverlayContent = .info(title: nil, text: "Thank you for your feedback.") + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let content: UndoOverlayContent = .info(title: nil, text: presentationData.strings.Chat_AudioTranscriptionFeedbackTip) controllerInteraction.displayUndo(content) }), false), at: 0) actions.insert(.separator, at: 1) @@ -2423,8 +2423,7 @@ private final class ChatRateTranscriptionContextItemNode: ASDisplayNode, Context self.textNode.isAccessibilityElement = false self.textNode.isUserInteractionEnabled = false self.textNode.displaysAsynchronously = false - //TODO:localizable - self.textNode.attributedText = NSAttributedString(string: "Rate Transcription", font: textFont, textColor: presentationData.theme.contextMenu.secondaryColor) + self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.Chat_AudioTranscriptionRateAction, font: textFont, textColor: presentationData.theme.contextMenu.secondaryColor) self.textNode.maximumNumberOfLines = 1 self.upButtonImageNode = ASImageNode() diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index eb360e5b94..48a2893fb6 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -33,7 +33,7 @@ private struct FetchControls { private enum TranscribedText { case success(text: String, isPending: Bool) - case error + case error(AudioTranscriptionMessageAttribute.TranscriptionError) } private func transcribedText(message: Message) -> TranscribedText? { @@ -45,7 +45,7 @@ private func transcribedText(message: Message) -> TranscribedText? { if attribute.isPending { return nil } else { - return .error + return .error(attribute.error ?? .generic) } } } @@ -413,7 +413,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } if let result = result { - let _ = arguments.context.engine.messages.storeLocallyTranscribedAudio(messageId: arguments.message.id, text: result.text, isFinal: result.isFinal).start() + let _ = arguments.context.engine.messages.storeLocallyTranscribedAudio(messageId: arguments.message.id, text: result.text, isFinal: result.isFinal, error: nil).start() } else { strongSelf.audioTranscriptionState = .collapsed strongSelf.requestUpdateLayout(true) @@ -658,10 +658,16 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { resultText += " [...]" } textString = NSAttributedString(string: resultText, font: textFont, textColor: messageTheme.primaryTextColor) - case .error: + case let .error(error): let errorTextFont = Font.regular(floor(arguments.presentationData.fontSize.baseDisplaySize * 15.0 / 17.0)) - //TODO:localize - textString = NSAttributedString(string: "No speech detected", font: errorTextFont, textColor: messageTheme.secondaryTextColor) + let errorText: String + switch error { + case .generic: + errorText = arguments.presentationData.strings.Message_AudioTranscription_ErrorEmpty + case .tooLong: + errorText = arguments.presentationData.strings.Message_AudioTranscription_ErrorTooLong + } + textString = NSAttributedString(string: errorText, font: errorTextFont, textColor: messageTheme.secondaryTextColor) } } else { textString = nil diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index c741da4568..7dd3cf86a3 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit c741da4568b0971ed06d9ccdc7a94db566bb84a0 +Subproject commit 7dd3cf86a3daa1ee8c1022930816cc8044d0ed5b diff --git a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.m b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.m index 302959802a..23d8c4e03d 100644 --- a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.m +++ b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.m @@ -102,7 +102,9 @@ static bool notyfyingShiftState = false; - (void)_65087dc8_setPreferredFrameRateRange:(CAFrameRateRange)range API_AVAILABLE(ios(15.0)) { if ([self associatedObjectForKey:forceFullRefreshRateKey] != nil) { float maxFps = [UIScreen mainScreen].maximumFramesPerSecond; - range = CAFrameRateRangeMake(maxFps, maxFps, maxFps); + if (maxFps > 61.0f) { + range = CAFrameRateRangeMake(maxFps, maxFps, maxFps); + } } [self _65087dc8_setPreferredFrameRateRange:range]; @@ -127,7 +129,9 @@ static bool notyfyingShiftState = false; if (@available(iOS 15.0, *)) { float maxFps = [UIScreen mainScreen].maximumFramesPerSecond; - [displayLink setPreferredFrameRateRange:CAFrameRateRangeMake(maxFps, maxFps, maxFps)]; + if (maxFps > 61.0f) { + [displayLink setPreferredFrameRateRange:CAFrameRateRangeMake(maxFps, maxFps, maxFps)]; + } } } }