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

This commit is contained in:
Ilya Laktyushin 2022-06-03 22:01:27 +04:00
commit 59fd09aaaa
29 changed files with 323 additions and 213 deletions

View File

@ -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";

View File

@ -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<EnginePeer.Id>), 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)

View File

@ -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
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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
}

View File

@ -34,6 +34,7 @@ swift_library(
"//submodules/ContextUI:ContextUI",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/TelegramUniversalVideoContent:TelegramUniversalVideoContent",
],
visibility = [
"//visibility:public",

View File

@ -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<Float, NoError>] = messages.compactMap { message -> Signal<Float, NoError>? 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) {

View File

@ -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<Float?, NoError>, dismissImmediately: Bool = false) {
func transitionToProgressWithValue(signal: Signal<Float?, NoError>, 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
}

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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) }

View File

@ -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

View File

@ -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) {

View File

@ -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:

View File

@ -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<AudioTranscriptionManager>?
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
))
})
}
}

View File

@ -64,8 +64,6 @@ public final class AccountStateManager {
let auxiliaryMethods: AccountAuxiliaryMethods
var transformOutgoingMessageMedia: TransformOutgoingMessageMedia?
let audioTranscriptionManager = Atomic<AudioTranscriptionManager>(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<AuthorizedAccountState, NoError> 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<AuthorizedAccountState, NoError> 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 {

View File

@ -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)
}
}

View File

@ -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")

View File

@ -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
}
}

View File

@ -329,16 +329,16 @@ public extension TelegramEngine {
}
public func transcribeAudio(messageId: MessageId) -> Signal<EngineAudioTranscriptionResult, NoError> {
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<Never, NoError> {
public func storeLocallyTranscribedAudio(messageId: MessageId, text: String, isFinal: Bool, error: AudioTranscriptionMessageAttribute.TranscriptionError?) -> 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))
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))
})

View File

@ -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<AudioTranscriptionManager>, messageId: MessageId) -> Signal<EngineAudioTranscriptionResult, NoError> {
func _internal_transcribeAudio(postbox: Postbox, network: Network, messageId: MessageId) -> Signal<EngineAudioTranscriptionResult, NoError> {
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<Api.messages.TranscribedAudio?, NoError> in
return .single(nil)
|> map { result -> Result<Api.messages.TranscribedAudio, AudioTranscriptionMessageAttribute.TranscriptionError> in
return .success(result)
}
|> `catch` { error -> Signal<Result<Api.messages.TranscribedAudio, AudioTranscriptionMessageAttribute.TranscriptionError>, 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<EngineAudioTranscriptionResult, NoError> 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

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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

@ -1 +1 @@
Subproject commit c741da4568b0971ed06d9ccdc7a94db566bb84a0
Subproject commit 7dd3cf86a3daa1ee8c1022930816cc8044d0ed5b

View File

@ -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)];
}
}
}
}