diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index c28ea26dda..02667eb150 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8159,3 +8159,6 @@ Sorry for the inconvenience."; "ChatList.EmptyTopicsTitle" = "No topics here yet"; "ChatList.EmptyTopicsCreate" = "Create New Topic"; "ChatList.EmptyTopicsShowAsMessages" = "Show as Messages"; + +"Message.AudioTranscription.SubscribeToPremium" = "Subscribe to **Telegram Premium** to convert voice to text."; +"Message.AudioTranscription.SubscribeToPremiumAction" = "More"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 4d9281adf1..84d60d1e93 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -793,6 +793,7 @@ public enum PremiumIntroSource { case deeplink(String?) case profile(PeerId) case emojiStatus(PeerId, Int64, TelegramMediaFile?, LoadedStickerPack?) + case voiceToText } #if ENABLE_WALLET diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index b5d6e16c22..4bd86d488e 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -30,10 +30,11 @@ public final class ChatMessageItemAssociatedData: Equatable { public let defaultReaction: MessageReaction.Reaction? public let isPremium: Bool public let forceInlineReactions: Bool + public let alwaysDisplayTranscribeButton: Bool public let accountPeer: EnginePeer? public let topicAuthorId: EnginePeer.Id? - public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: EngineMessage.Index? = nil, isCopyProtectionEnabled: Bool = false, availableReactions: AvailableReactions?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, accountPeer: EnginePeer?, forceInlineReactions: Bool = false, topicAuthorId: EnginePeer.Id? = nil) { + public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: EngineMessage.Index? = nil, isCopyProtectionEnabled: Bool = false, availableReactions: AvailableReactions?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, accountPeer: EnginePeer?, forceInlineReactions: Bool = false, alwaysDisplayTranscribeButton: Bool = false, topicAuthorId: EnginePeer.Id? = nil) { self.automaticDownloadPeerType = automaticDownloadPeerType self.automaticDownloadNetworkType = automaticDownloadNetworkType self.isRecentActions = isRecentActions @@ -51,6 +52,7 @@ public final class ChatMessageItemAssociatedData: Equatable { self.accountPeer = accountPeer self.forceInlineReactions = forceInlineReactions self.topicAuthorId = topicAuthorId + self.alwaysDisplayTranscribeButton = alwaysDisplayTranscribeButton } public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool { @@ -102,6 +104,9 @@ public final class ChatMessageItemAssociatedData: Equatable { if lhs.topicAuthorId != rhs.topicAuthorId { return false } + if lhs.alwaysDisplayTranscribeButton != rhs.alwaysDisplayTranscribeButton { + return false + } return true } } diff --git a/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift b/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift index b35ac1aa3e..4aa47030ad 100644 --- a/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift +++ b/submodules/ChatMessageBackground/Sources/ChatMessageBackground.swift @@ -107,6 +107,50 @@ public class ChatMessageBackground: ASDisplayNode { } } + public func currentCorners(bubbleCorners: PresentationChatBubbleCorners) -> (topLeftRadius: CGFloat, topRightRadius: CGFloat, bottomLeftRadius: CGFloat, bottomRightRadius: CGFloat, drawTail: Bool)? { + guard let type = self.type else { + return nil + } + + let maxRadius = bubbleCorners.mainRadius + let minRadius = bubbleCorners.auxiliaryRadius + + switch type { + case .none: + return nil + case let .incoming(mergeType): + switch mergeType { + case .None: + return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: true, neighbors: .none) + case let .Top(side): + return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: true, neighbors: .top(side: side)) + case .Bottom: + return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: true, neighbors: .bottom) + case .Both: + return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: true, neighbors: .both) + case .Side: + return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: true, neighbors: .side) + case .Extracted: + return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: true, neighbors: .extracted) + } + case let .outgoing(mergeType): + switch mergeType { + case .None: + return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: false, neighbors: .none) + case let .Top(side): + return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: false, neighbors: .top(side: side)) + case .Bottom: + return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: false, neighbors: .bottom) + case .Both: + return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: false, neighbors: .both) + case .Side: + return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: false, neighbors: .side) + case .Extracted: + return messageBubbleArguments(maxCornerRadius: maxRadius, minCornerRadius: minRadius, incoming: false, neighbors: .extracted) + } + } + } + public func setType(type: ChatMessageBackgroundType, highlighted: Bool, graphics: PrincipalThemeEssentialGraphics, maskMode: Bool, hasWallpaper: Bool, transition: ContainedViewLayoutTransition, backgroundNode: WallpaperBackgroundNode?) { let previousType = self.type if let currentType = previousType, currentType == type, self.currentHighlighted == highlighted, self.graphics === graphics, backgroundNode === self.backgroundNode, self.maskMode == maskMode, self.hasWallpaper == hasWallpaper { diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index b69f9668c2..6bb01fc170 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -1265,7 +1265,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa case .privateChannel: let invite = (view.cachedData as? CachedGroupData)?.exportedInvitation entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased())) - entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, !isInitialSetup)) + entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, !isInitialSetup)) entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp)) switch mode { case .initialSetup, .revokeNames: @@ -1413,6 +1413,9 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta let revokeAddressNameDisposable = MetaDisposable() actionsDisposable.add(revokeAddressNameDisposable) + let deactivateAllAddressNamesDisposable = MetaDisposable() + actionsDisposable.add(deactivateAllAddressNamesDisposable) + let revokeLinkDisposable = MetaDisposable() actionsDisposable.add(revokeLinkDisposable) @@ -1433,17 +1436,20 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta queue: Queue.mainQueue(), adminedPublicChannels.get() |> filter { $0 != nil } |> take(1), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)), + context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)), context.engine.data.get( TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) ) - ).start(next: { peers, accountPeer, data in + ).start(next: { peers, accountPeer, peer, data in let (limits, premiumLimits) = data let isPremium = accountPeer?.isPremium ?? false + let hasAdditionalUsernames = (peer?._asPeer().usernames.firstIndex(where: { !$0.flags.contains(.isEditable) }) ?? nil) != nil + if let peers = peers { let count = Int32(peers.count) - if count < limits.maxPublicLinksCount || (count < premiumLimits.maxPublicLinksCount && isPremium) { + if count < limits.maxPublicLinksCount || (count < premiumLimits.maxPublicLinksCount && isPremium) || hasAdditionalUsernames { updateState { state in return state.withUpdatedSelectedType(type) } @@ -1820,6 +1826,10 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta return state } + if let selectedType = state.selectedType, case .privateChannel = selectedType, peer.usernames.firstIndex(where: { $0.isActive && !$0.flags.contains(.isEditable) }) != nil { + deactivateAllAddressNamesDisposable.set(context.engine.peers.deactivateAllAddressNames(peerId: peerId).start()) + } + if let updatedCopyProtection = state.forwardingEnabled { toggleCopyProtectionDisposable.set(context.engine.peers.toggleMessageCopyProtection(peerId: peerId, enabled: !updatedCopyProtection).start()) } diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 907e9d6b15..7e1cf67650 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -153,6 +153,12 @@ public enum PremiumSource: Equatable { } else { return false } + case .voiceToText: + if case .voiceToText = rhs { + return true + } else { + return false + } } } @@ -177,6 +183,7 @@ public enum PremiumSource: Equatable { case emojiStatus(PeerId, Int64, TelegramMediaFile?, LoadedStickerPack?) case gift(from: PeerId, to: PeerId, duration: Int32) case giftTerms + case voiceToText var identifier: String? { switch self { @@ -216,6 +223,8 @@ public enum PremiumSource: Equatable { return "profile__\(id.id._internalGetInt64Value())" case .emojiStatus: return "emoji_status" + case .voiceToText: + return "voice_to_text" case .gift, .giftTerms: return nil case let .deeplink(reference): diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift index 2fb8ac007f..0bb6585333 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift @@ -185,7 +185,7 @@ func _internal_deactivateAllAddressNames(account: Account, peerId: EnginePeer.Id } |> mapToSignal { result -> Signal in return account.postbox.transaction { transaction -> Signal in - if case .boolTrue = result, let peer = transaction.getPeer(account.peerId) as? TelegramChannel { + if case .boolTrue = result, let peer = transaction.getPeer(peerId) as? TelegramChannel { var updatedNames: [TelegramPeerUsername] = [] for username in peer.usernames { var updatedFlags = username.flags diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index 33fc816bfa..7ccc7097a8 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -164,6 +164,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case sharedMediaFastScrollingTooltip = 30 case forcedPasswordSetup = 31 case emojiTooltip = 32 + case audioTranscriptionSuggestion = 33 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) @@ -344,6 +345,10 @@ private struct ApplicationSpecificNoticeKeys { static func emojiTooltip() -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.emojiTooltip.key) } + + static func audioTranscriptionSuggestion() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.audioTranscriptionSuggestion.key) + } } public struct ApplicationSpecificNotice { @@ -1140,6 +1145,36 @@ public struct ApplicationSpecificNotice { return engine.notices.set(id: ApplicationSpecificNoticeKeys.forcedPasswordSetup(), item: item) } + public static func audioTranscriptionSuggestionKey() -> NoticeEntryKey { + return ApplicationSpecificNoticeKeys.audioTranscriptionSuggestion() + } + + public static func getAudioTranscriptionSuggestion(accountManager: AccountManager) -> Signal { + return accountManager.transaction { transaction -> Int32 in + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.audioTranscriptionSuggestion())?.get(ApplicationSpecificCounterNotice.self) { + return value.value + } else { + return 0 + } + } + } + + public static func incrementAudioTranscriptionSuggestion(accountManager: AccountManager, count: Int = 1) -> Signal { + return accountManager.transaction { transaction -> Int in + var currentValue: Int32 = 0 + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.audioTranscriptionSuggestion())?.get(ApplicationSpecificCounterNotice.self) { + currentValue = value.value + } + let previousValue = currentValue + currentValue += Int32(count) + + if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + transaction.setNotice(ApplicationSpecificNoticeKeys.audioTranscriptionSuggestion(), entry) + } + + return Int(previousValue) + } + } public static func reset(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in } diff --git a/submodules/TelegramPresentationData/Sources/ChatMessageBubbleImages.swift b/submodules/TelegramPresentationData/Sources/ChatMessageBubbleImages.swift index 6897877f0e..9ae6a5dd69 100644 --- a/submodules/TelegramPresentationData/Sources/ChatMessageBubbleImages.swift +++ b/submodules/TelegramPresentationData/Sources/ChatMessageBubbleImages.swift @@ -84,6 +84,65 @@ public func messageBubbleImage(maxCornerRadius: CGFloat, minCornerRadius: CGFloa return messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: incoming, fillColor: fillColor, strokeColor: strokeColor, neighbors: neighbors, shadow: bubbleColors.bubble.withWallpaper.shadow, wallpaper: wallpaper, knockout: knockoutValue, mask: mask, extendedEdges: extendedEdges, onlyOutline: onlyOutline, onlyShadow: onlyShadow, alwaysFillColor: alwaysFillColor) } +public func messageBubbleArguments(maxCornerRadius: CGFloat, minCornerRadius: CGFloat, incoming: Bool, neighbors: MessageBubbleImageNeighbors) -> (topLeftRadius: CGFloat, topRightRadius: CGFloat, bottomLeftRadius: CGFloat, bottomRightRadius: CGFloat, drawTail: Bool) { + var topLeftRadius: CGFloat + var topRightRadius: CGFloat + var bottomLeftRadius: CGFloat + var bottomRightRadius: CGFloat + var drawTail: Bool + + switch neighbors { + case .none: + topLeftRadius = maxCornerRadius + topRightRadius = maxCornerRadius + bottomLeftRadius = maxCornerRadius + bottomRightRadius = maxCornerRadius + drawTail = true + case .both: + topLeftRadius = maxCornerRadius + topRightRadius = minCornerRadius + bottomLeftRadius = maxCornerRadius + bottomRightRadius = minCornerRadius + drawTail = false + case .bottom: + topLeftRadius = maxCornerRadius + topRightRadius = minCornerRadius + bottomLeftRadius = maxCornerRadius + bottomRightRadius = maxCornerRadius + drawTail = true + case .side: + topLeftRadius = maxCornerRadius + topRightRadius = maxCornerRadius + bottomLeftRadius = minCornerRadius + bottomRightRadius = minCornerRadius + drawTail = false + case let .top(side): + topLeftRadius = maxCornerRadius + topRightRadius = maxCornerRadius + bottomLeftRadius = side ? minCornerRadius : maxCornerRadius + bottomRightRadius = minCornerRadius + drawTail = false + case .extracted: + topLeftRadius = maxCornerRadius + topRightRadius = maxCornerRadius + bottomLeftRadius = maxCornerRadius + bottomRightRadius = maxCornerRadius + drawTail = false + } + + if incoming { + var tmp = topRightRadius + topRightRadius = topLeftRadius + topLeftRadius = tmp + + tmp = bottomRightRadius + bottomRightRadius = bottomLeftRadius + bottomLeftRadius = tmp + } + + return (topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius, drawTail) +} + public func messageBubbleImage(maxCornerRadius: CGFloat, minCornerRadius: CGFloat, incoming: Bool, fillColor: UIColor, strokeColor: UIColor, neighbors: MessageBubbleImageNeighbors, shadow: PresentationThemeBubbleShadow?, wallpaper: TelegramWallpaper, knockout knockoutValue: Bool, mask: Bool = false, extendedEdges: Bool = false, onlyOutline: Bool = false, onlyShadow: Bool = false, alwaysFillColor: Bool = false) -> UIImage { let topLeftRadius: CGFloat let topRightRadius: CGFloat diff --git a/submodules/TelegramUI/Resources/Animations/anim_voiceToText.json b/submodules/TelegramUI/Resources/Animations/anim_voiceToText.json new file mode 100644 index 0000000000..f1130e622d --- /dev/null +++ b/submodules/TelegramUI/Resources/Animations/anim_voiceToText.json @@ -0,0 +1 @@ +{"v":"5.9.6","fr":60,"ip":0,"op":120,"w":512,"h":512,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Artboard Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":8,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":9,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":10,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":11,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":12,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":13,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":14,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":15,"s":[0]},{"i":{"x":[0.833],"y":[0.479]},"o":{"x":[0.167],"y":[0]},"t":16,"s":[0]},{"i":{"x":[0.833],"y":[0.715]},"o":{"x":[0.167],"y":[0.083]},"t":17,"s":[0]},{"i":{"x":[0.833],"y":[0.865]},"o":{"x":[0.167],"y":[0.118]},"t":18,"s":[6.247]},{"i":{"x":[0.833],"y":[0.742]},"o":{"x":[0.167],"y":[0.219]},"t":19,"s":[21.333]},{"i":{"x":[0.833],"y":[0.839]},"o":{"x":[0.167],"y":[0.123]},"t":20,"s":[30.627]},{"i":{"x":[0.833],"y":[0.88]},"o":{"x":[0.167],"y":[0.172]},"t":21,"s":[50.16]},{"i":{"x":[0.833],"y":[0.781]},"o":{"x":[0.167],"y":[0.273]},"t":22,"s":[68.438]},{"i":{"x":[0.833],"y":[0.86]},"o":{"x":[0.167],"y":[0.134]},"t":23,"s":[76.448]},{"i":{"x":[0.833],"y":[0.889]},"o":{"x":[0.167],"y":[0.207]},"t":24,"s":[89.501]},{"i":{"x":[0.833],"y":[0.828]},"o":{"x":[0.167],"y":[0.339]},"t":25,"s":[98.316]},{"i":{"x":[0.833],"y":[0.911]},"o":{"x":[0.167],"y":[0.161]},"t":26,"s":[101.192]},{"i":{"x":[0.833],"y":[1.198]},"o":{"x":[0.167],"y":[1.343]},"t":27,"s":[104.264]},{"i":{"x":[0.833],"y":[0.627]},"o":{"x":[0.167],"y":[0.059]},"t":28,"s":[104.467]},{"i":{"x":[0.833],"y":[0.809]},"o":{"x":[0.167],"y":[0.107]},"t":29,"s":[103.781]},{"i":{"x":[0.833],"y":[0.873]},"o":{"x":[0.167],"y":[0.148]},"t":30,"s":[101.394]},{"i":{"x":[0.833],"y":[0.76]},"o":{"x":[0.167],"y":[0.244]},"t":31,"s":[98.311]},{"i":{"x":[0.833],"y":[0.847]},"o":{"x":[0.167],"y":[0.128]},"t":32,"s":[96.713]},{"i":{"x":[0.833],"y":[0.883]},"o":{"x":[0.167],"y":[0.184]},"t":33,"s":[93.707]},{"i":{"x":[0.833],"y":[0.793]},"o":{"x":[0.167],"y":[0.291]},"t":34,"s":[91.209]},{"i":{"x":[0.833],"y":[0.87]},"o":{"x":[0.167],"y":[0.14]},"t":35,"s":[90.206]},{"i":{"x":[0.833],"y":[0.897]},"o":{"x":[0.167],"y":[0.233]},"t":36,"s":[88.722]},{"i":{"x":[0.833],"y":[0.891]},"o":{"x":[0.167],"y":[0.428]},"t":37,"s":[87.893]},{"i":{"x":[0.833],"y":[1.284]},"o":{"x":[0.167],"y":[0.359]},"t":38,"s":[87.692]},{"i":{"x":[0.833],"y":[0.851]},"o":{"x":[0.167],"y":[0.064]},"t":39,"s":[87.632]},{"i":{"x":[0.833],"y":[0.72]},"o":{"x":[0.167],"y":[0.189]},"t":40,"s":[87.899]},{"i":{"x":[0.833],"y":[0.831]},"o":{"x":[0.167],"y":[0.119]},"t":41,"s":[88.111]},{"i":{"x":[0.833],"y":[0.878]},"o":{"x":[0.167],"y":[0.164]},"t":42,"s":[88.609]},{"i":{"x":[0.833],"y":[0.773]},"o":{"x":[0.167],"y":[0.262]},"t":43,"s":[89.124]},{"i":{"x":[0.833],"y":[0.855]},"o":{"x":[0.167],"y":[0.132]},"t":44,"s":[89.364]},{"i":{"x":[0.833],"y":[0.887]},"o":{"x":[0.167],"y":[0.196]},"t":45,"s":[89.778]},{"i":{"x":[0.833],"y":[0.81]},"o":{"x":[0.167],"y":[0.314]},"t":46,"s":[90.085]},{"i":{"x":[0.833],"y":[0.887]},"o":{"x":[0.167],"y":[0.148]},"t":47,"s":[90.195]},{"i":{"x":[0.833],"y":[0.919]},"o":{"x":[0.167],"y":[0.314]},"t":48,"s":[90.337]},{"i":{"x":[0.833],"y":[-1.909]},"o":{"x":[0.167],"y":[-3.478]},"t":49,"s":[90.389]},{"i":{"x":[0.833],"y":[0.768]},"o":{"x":[0.167],"y":[0.086]},"t":50,"s":[90.387]},{"i":{"x":[0.833],"y":[0.869]},"o":{"x":[0.167],"y":[0.13]},"t":51,"s":[90.347]},{"i":{"x":[0.833],"y":[0.748]},"o":{"x":[0.167],"y":[0.228]},"t":52,"s":[90.274]},{"i":{"x":[0.833],"y":[0.842]},"o":{"x":[0.167],"y":[0.125]},"t":53,"s":[90.233]},{"i":{"x":[0.833],"y":[0.881]},"o":{"x":[0.167],"y":[0.176]},"t":54,"s":[90.148]},{"i":{"x":[0.833],"y":[0.785]},"o":{"x":[0.167],"y":[0.279]},"t":55,"s":[90.073]},{"i":{"x":[0.833],"y":[0.863]},"o":{"x":[0.167],"y":[0.136]},"t":56,"s":[90.04]},{"i":{"x":[0.833],"y":[0.891]},"o":{"x":[0.167],"y":[0.213]},"t":57,"s":[89.989]},{"i":{"x":[0.833],"y":[0.84]},"o":{"x":[0.167],"y":[0.356]},"t":58,"s":[89.956]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.174]},"t":59,"s":[89.946]},{"t":60,"s":[89.937]}],"ix":10},"p":{"a":0,"k":[256,256,0],"ix":2,"l":2},"a":{"a":0,"k":[150,150,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[-0.154,-0.154,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.633,0.633,0]},"t":8,"s":[136.531,136.531,100]},{"i":{"x":[0.833,0.833,0.833],"y":[-0.395,-0.395,1]},"o":{"x":[0.167,0.167,0.167],"y":[1.154,1.154,0]},"t":9,"s":[136.531,136.531,100]},{"i":{"x":[0.833,0.833,0.833],"y":[-0.828,-0.828,1]},"o":{"x":[0.167,0.167,0.167],"y":[1.395,1.395,0]},"t":10,"s":[136.531,136.531,100]},{"i":{"x":[0.833,0.833,0.833],"y":[-1.604,-1.604,1]},"o":{"x":[0.167,0.167,0.167],"y":[1.828,1.828,0]},"t":11,"s":[136.531,136.531,100]},{"i":{"x":[0.833,0.833,0.833],"y":[-1.571,-1.571,1]},"o":{"x":[0.167,0.167,0.167],"y":[2.604,2.604,0]},"t":12,"s":[136.531,136.531,100]},{"i":{"x":[0.833,0.833,0.833],"y":[-0.785,-0.785,1]},"o":{"x":[0.167,0.167,0.167],"y":[2.571,2.571,0]},"t":13,"s":[136.531,136.531,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.708,0.708,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.362,0.362,0]},"t":14,"s":[136.531,136.531,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.885,0.885,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.134,0.134,0]},"t":15,"s":[141.464,141.464,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.809,0.809,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.306,0.306,0]},"t":16,"s":[152.189,152.189,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.891,0.891,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.148,0.148,0]},"t":17,"s":[156.208,156.208,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.939,0.939,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.357,0.357,0]},"t":18,"s":[161.42,161.42,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.357,0.357,1]},"o":{"x":[0.167,0.167,0.167],"y":[-0.228,-0.228,0]},"t":19,"s":[163.007,163.007,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.776,0.776,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.096,0.096,0]},"t":20,"s":[162.582,162.582,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.867,0.867,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.133,0.133,0]},"t":21,"s":[159.727,159.727,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.739,0.739,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.223,0.223,0]},"t":22,"s":[154.892,154.892,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.835,0.835,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.122,0.122,0]},"t":23,"s":[152.008,152.008,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.878,0.878,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.168,0.168,0]},"t":24,"s":[145.843,145.843,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.774,0.774,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.265,0.265,0]},"t":25,"s":[139.793,139.793,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.857,0.857,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.132,0.132,0]},"t":26,"s":[137.015,137.015,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.888,0.888,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.199,0.199,0]},"t":27,"s":[132.26,132.26,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.827,0.827,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.327,0.327,0]},"t":28,"s":[128.827,128.827,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.92,0.92,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.161,0.161,0]},"t":29,"s":[127.655,127.655,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.264,0.264,1]},"o":{"x":[0.167,0.167,0.167],"y":[-1.748,-1.748,0]},"t":30,"s":[126.391,126.391,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.629,0.629,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.094,0.094,0]},"t":31,"s":[126.449,126.449,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.802,0.802,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.107,0.107,0]},"t":32,"s":[126.899,126.899,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.871,0.871,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.144,0.144,0]},"t":33,"s":[128.454,128.454,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.748,0.748,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.234,0.234,0]},"t":34,"s":[130.597,130.597,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.84,0.84,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.124,0.124,0]},"t":35,"s":[131.782,131.782,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.88,0.88,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.174,0.174,0]},"t":36,"s":[134.184,134.184,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.781,0.781,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.273,0.273,0]},"t":37,"s":[136.404,136.404,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.862,0.862,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.135,0.135,0]},"t":38,"s":[137.378,137.378,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.892,0.892,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.211,0.211,0]},"t":39,"s":[138.962,138.962,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.859,0.859,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.366,0.366,0]},"t":40,"s":[139.998,139.998,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1.018,1.018,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.205,0.205,0]},"t":41,"s":[140.303,140.303,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.829,0.829,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.015,0.015,0]},"t":42,"s":[140.514,140.514,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.688,0.688,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.163,0.163,0]},"t":43,"s":[140.257,140.257,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.816,0.816,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.114,0.114,0]},"t":44,"s":[139.988,139.988,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.873,0.873,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.152,0.152,0]},"t":45,"s":[139.248,139.248,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.756,0.756,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.243,0.243,0]},"t":46,"s":[138.35,138.35,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.844,0.844,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.126,0.126,0]},"t":47,"s":[137.882,137.882,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.882,0.882,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.179,0.179,0]},"t":48,"s":[136.976,136.976,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.789,0.789,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.283,0.283,0]},"t":49,"s":[136.186,136.186,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.869,0.869,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.138,0.138,0]},"t":50,"s":[135.857,135.857,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.898,0.898,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.23,0.23,0]},"t":51,"s":[135.352,135.352,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.942,0.942,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.463,0.463,0]},"t":52,"s":[135.066,135.066,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.124,0.124,1]},"o":{"x":[0.167,0.167,0.167],"y":[-0.196,-0.196,0]},"t":53,"s":[135.003,135.003,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.854,0.854,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.092,0.092,0]},"t":54,"s":[135.022,135.022,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.714,0.714,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.194,0.194,0]},"t":55,"s":[135.2,135.2,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.824,0.824,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.118,0.118,0]},"t":56,"s":[135.334,135.334,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.875,0.875,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.159,0.159,0]},"t":57,"s":[135.66,135.66,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.762,0.762,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.251,0.251,0]},"t":58,"s":[136.021,136.021,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.128,0.128,0]},"t":59,"s":[136.2,136.2,100]},{"t":60,"s":[136.531,136.531,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":19,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[150,80],[150,150]],"c":false}]},{"t":44,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[170,50],[170,120]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":20,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":19,"s":[{"i":[[0,0],[0,0],[0,-10],[0,0]],"o":[[0,0],[0,10],[0,0],[0,0]],"v":[[150,80],[150,100],[150,130],[150,150]],"c":false}]},{"t":44,"s":[{"i":[[0,0],[0,0],[-9.267,-4.333],[0,0]],"o":[[0,0],[-9.267,4.333],[0,0],[0,0]],"v":[[200,35],[94.267,75.667],[94.267,94.333],[200,135]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":19,"s":[100]},{"t":33,"s":[20]}],"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":19,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[150,270],[150,230]],"c":false}]},{"t":44,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[150,270],[150,185]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":20,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":19,"s":[{"i":[[0,0],[-44.183,0],[0,44.183]],"o":[[0,44.183],[44.183,0],[0,0]],"v":[[-80,-40],[0,40],[80,-40]],"c":false}]},{"t":36,"s":[{"i":[[0,0],[0,0],[-10,-10]],"o":[[10,-10],[0,0],[0,0]],"v":[[-50,30],[0,-15],[50,30]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":20,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[150,190],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":123,"st":0,"ct":1,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index b23fc547fe..d4ac151ef2 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -20,6 +20,7 @@ import ChatListUI import ComponentFlow import ReactionSelectionNode import ChatPresentationInterfaceState +import TelegramNotices extension ChatReplyThreadMessage { var effectiveTopId: MessageId { @@ -314,7 +315,7 @@ private final class ChatHistoryTransactionOpaqueState { } } -private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]], subject: ChatControllerSubject?, currentlyPlayingMessageId: MessageIndex?, isCopyProtectionEnabled: Bool, availableReactions: AvailableReactions?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, accountPeer: EnginePeer?, topicAuthorId: EnginePeer.Id?) -> ChatMessageItemAssociatedData { +private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]], subject: ChatControllerSubject?, currentlyPlayingMessageId: MessageIndex?, isCopyProtectionEnabled: Bool, availableReactions: AvailableReactions?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, alwaysDisplayTranscribeButton: Bool, accountPeer: EnginePeer?, topicAuthorId: EnginePeer.Id?) -> ChatMessageItemAssociatedData { var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel var contactsPeerIds: Set = Set() var channelDiscussionGroup: ChatMessageItemAssociatedData.ChannelDiscussionGroupStatus = .unknown @@ -363,7 +364,7 @@ private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHist } } - return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, topicAuthorId: topicAuthorId) + return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, topicAuthorId: topicAuthorId) } private extension ChatHistoryLocationInput { @@ -1031,7 +1032,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { return peer } |> distinctUntilChanged - + let topicAuthorId: Signal if let peerId = chatLocation.peerId, let threadId = chatLocation.threadId { topicAuthorId = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.ThreadData(id: peerId, threadId: threadId)) @@ -1042,6 +1043,19 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } else { topicAuthorId = .single(nil) } + + let suggestAudioTranscription = context.engine.data.get(TelegramEngine.EngineData.Item.Notices.Notice(key: ApplicationSpecificNotice.audioTranscriptionSuggestionKey())) + |> map { entry -> Int32 in + return entry?.get(ApplicationSpecificCounterNotice.self)?.value ?? 0 + } + + let promises = combineLatest( + self.historyAppearsClearedPromise.get(), + self.pendingUnpinnedAllMessagesPromise.get(), + self.pendingRemovedMessagesPromise.get(), + self.currentlyPlayingMessageIdPromise.get(), + self.scrollToMessageIdPromise.get() + ) let historyViewTransitionDisposable = combineLatest(queue: messageViewQueue, historyViewUpdate, @@ -1049,21 +1063,19 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { selectedMessages, updatingMedia, automaticDownloadNetworkType, - self.historyAppearsClearedPromise.get(), - self.pendingUnpinnedAllMessagesPromise.get(), - self.pendingRemovedMessagesPromise.get(), animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, - self.currentlyPlayingMessageIdPromise.get(), - self.scrollToMessageIdPromise.get(), adMessages, availableReactions, defaultReaction, accountPeer, + suggestAudioTranscription, + promises, topicAuthorId - ).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, currentlyPlayingMessageIdAndType, scrollToMessageId, adMessages, availableReactions, defaultReaction, accountPeer, topicAuthorId in + ).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, adMessages, availableReactions, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId in + let (historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, currentlyPlayingMessageIdAndType, scrollToMessageId) = promises let currentlyPlayingMessageId = currentlyPlayingMessageIdAndType?.0 func applyHole() { @@ -1197,7 +1209,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { isPremium = true } - let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, topicAuthorId: topicAuthorId) + let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, alwaysDisplayTranscribeButton: suggestAudioTranscription < 2, accountPeer: accountPeer, topicAuthorId: topicAuthorId) let filteredEntries = chatHistoryEntriesForView( location: chatLocation, diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 9e609da052..33e9088eb5 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -2903,8 +2903,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode let contentNode = strongSelf.contentNodes[contentNodeIndex] + var useContentOrigin = useContentOrigin if contentNode.disablesClipping { shouldClipOnTransitions = false + useContentOrigin = false } let contentNodeFrame = relativeFrame.offsetBy(dx: contentOrigin.x, dy: useContentOrigin ? contentOrigin.y : 0.0) diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoBubbleContentNode.swift index fdaabade8b..ae0f691df5 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoBubbleContentNode.swift @@ -17,7 +17,7 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode { private let maskForeground = SimpleLayer() private let backdropMaskLayer = SimpleLayer() - private let backdropMaskForeground = SimpleShapeLayer() + private let backdropMaskForeground = BubbleMaskLayer() private var isExpanded = false @@ -48,10 +48,7 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode { self.maskForeground.backgroundColor = UIColor.white.cgColor self.maskForeground.masksToBounds = true self.maskLayer.addSublayer(self.maskForeground) - - self.backdropMaskForeground.fillColor = UIColor.white.cgColor - self.backdropMaskForeground.masksToBounds = true - + self.addSubnode(self.interactiveFileNode) self.addSubnode(self.interactiveVideoNode) @@ -136,8 +133,7 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode { required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - + override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { let interactiveVideoLayout = self.interactiveVideoNode.asyncLayout() let interactiveFileLayout = self.interactiveFileNode.asyncLayout() @@ -292,16 +288,37 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode { let maskFrame = CGRect(origin: CGPoint(x: isExpanded ? 1.0 : (incoming ? 7.0 : 1.0), y: isExpanded ? 0.0 : 1.0), size: isExpanded ? finalSize : CGSize(width: radius * 2.0, height: radius * 2.0)) animation.animator.updateCornerRadius(layer: strongSelf.maskForeground, cornerRadius: maskCornerRadius, completion: nil) animation.animator.updateFrame(layer: strongSelf.maskForeground, frame: maskFrame, completion: nil) - - let backdropMaskFrame = CGRect(origin: CGPoint(x: isExpanded ? (incoming ? 8.0 : 2.0) : (incoming ? 8.0 : 2.0), y: isExpanded ? 2.0 : 2.0), size: isExpanded ? CGSize(width: finalSize.width - 11.0, height: finalSize.height - 2.0) : CGSize(width: radius * 2.0, height: radius * 2.0)) - -// let auxiliaryRadius = item.presentationData.chatBubbleCorners.auxiliaryRadius - let backdropRadius = isExpanded ? item.presentationData.chatBubbleCorners.mainRadius : radius - let path = CGPath(roundedRect: backdropMaskFrame, cornerWidth: backdropRadius, cornerHeight: backdropRadius, transform: nil) - strongSelf.backdropMaskForeground.frame = strongSelf.maskLayer.frame - animation.transition.updatePath(layer: strongSelf.backdropMaskForeground, path: path) - + let backdropMaskFrame = CGRect(origin: CGPoint(x: isExpanded ? (incoming ? 8.0 : 2.0) : (incoming ? 8.0 : 2.0), y: isExpanded ? 2.0 : 2.0), size: isExpanded ? CGSize(width: finalSize.width - 11.0, height: finalSize.height - 2.0) : CGSize(width: radius * 2.0, height: radius * 2.0)) + + + let topLeftCornerRadius: CGFloat + let topRightCornerRadius: CGFloat + let bottomLeftCornerRadius: CGFloat + let bottomRightCornerRadius: CGFloat + if let bubbleCorners = strongSelf.bubbleBackgroundNode?.currentCorners(bubbleCorners: item.presentationData.chatBubbleCorners) { + topLeftCornerRadius = isExpanded ? bubbleCorners.topLeftRadius : radius + topRightCornerRadius = isExpanded ? bubbleCorners.topRightRadius : radius + bottomLeftCornerRadius = isExpanded ? bubbleCorners.bottomLeftRadius : radius + bottomRightCornerRadius = isExpanded ? bubbleCorners.bottomRightRadius : radius + } else { + let backdropRadius = isExpanded ? item.presentationData.chatBubbleCorners.mainRadius : radius + topLeftCornerRadius = backdropRadius + topRightCornerRadius = backdropRadius + bottomLeftCornerRadius = backdropRadius + bottomRightCornerRadius = backdropRadius + } + + strongSelf.backdropMaskForeground.update( + size: backdropMaskFrame.size, + topLeftCornerRadius: topLeftCornerRadius, + topRightCornerRadius: topRightCornerRadius, + bottomLeftCornerRadius: bottomLeftCornerRadius, + bottomRightCornerRadius: bottomRightCornerRadius, + animator: animation.animator + ) + animation.animator.updateFrame(layer: strongSelf.backdropMaskForeground, frame: backdropMaskFrame, completion: nil) + let videoLayoutData: ChatMessageInstantVideoItemLayoutData if incoming { videoLayoutData = .constrained(left: 0.0, right: 0.0) //max(0.0, availableContentWidth - videoFrame.width)) @@ -405,3 +422,92 @@ class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentNode { return true } } + +private class BubbleMaskLayer: SimpleLayer { + private class CornerLayer: SimpleLayer { + private let contentLayer = SimpleLayer() + + override init(layer: Any) { + super.init(layer: layer) + } + + init(cornerMask: CACornerMask) { + super.init() + self.masksToBounds = true + + self.contentLayer.backgroundColor = UIColor.white.cgColor + self.contentLayer.masksToBounds = true + self.contentLayer.maskedCorners = cornerMask + self.addSublayer(self.contentLayer) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(size: CGSize, cornerRadius: CGFloat, animator: ControlledTransitionAnimator) { + animator.updateCornerRadius(layer: self.contentLayer, cornerRadius: cornerRadius, completion: nil) + + let mask = self.contentLayer.maskedCorners + var origin = CGPoint() + if mask == .layerMinXMinYCorner { + origin = .zero + } else if mask == .layerMaxXMinYCorner { + origin = CGPoint(x: -size.width / 2.0, y: 0.0) + } else if mask == .layerMinXMaxYCorner { + origin = CGPoint(x: 0.0, y: -size.height / 2.0) + } else if mask == .layerMaxXMaxYCorner { + origin = CGPoint(x: -size.width / 2.0, y: -size.height / 2.0) + } + animator.updateFrame(layer: self.contentLayer, frame: CGRect(origin: origin, size: size), completion: nil) + } + } + + private let topLeft = CornerLayer(cornerMask: [.layerMinXMinYCorner]) + private let topRight = CornerLayer(cornerMask: [.layerMaxXMinYCorner]) + private let bottomLeft = CornerLayer(cornerMask: [.layerMinXMaxYCorner]) + private let bottomRight = CornerLayer(cornerMask: [.layerMaxXMaxYCorner]) + + override init(layer: Any) { + super.init(layer: layer) + } + + override init() { + super.init() + + self.addSublayer(self.topLeft) + self.addSublayer(self.topRight) + self.addSublayer(self.bottomLeft) + self.addSublayer(self.bottomRight) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update( + size: CGSize, + topLeftCornerRadius: CGFloat, + topRightCornerRadius: CGFloat, + bottomLeftCornerRadius: CGFloat, + bottomRightCornerRadius: CGFloat, + animator: ControlledTransitionAnimator + ) { + var size = CGSize(width: floor(size.width), height: floor(size.height)) + if Int(size.width) % 2 != 0 { + size.width += 1.0 + } + if Int(size.height) % 2 != 0 { + size.height += 1.0 + } + animator.updateFrame(layer: self.topLeft, frame: CGRect(origin: .zero, size: CGSize(width: size.width / 2.0, height: size.height / 2.0)), completion: nil) + animator.updateFrame(layer: self.topRight, frame: CGRect(origin: CGPoint(x: size.width / 2.0, y: 0.0), size: CGSize(width: size.width / 2.0, height: size.height / 2.0)), completion: nil) + animator.updateFrame(layer: self.bottomLeft, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height / 2.0), size: CGSize(width: size.width / 2.0, height: size.height / 2.0)), completion: nil) + animator.updateFrame(layer: self.bottomRight, frame: CGRect(origin: CGPoint(x: size.width / 2.0, y: size.height / 2.0), size: CGSize(width: size.width / 2.0, height: size.height / 2.0)), completion: nil) + + self.topLeft.update(size: size, cornerRadius: topLeftCornerRadius, animator: animator) + self.topRight.update(size: size, cornerRadius: topRightCornerRadius, animator: animator) + self.bottomLeft.update(size: size, cornerRadius: bottomLeftCornerRadius, animator: animator) + self.bottomRight.update(size: size, cornerRadius: bottomRightCornerRadius, animator: animator) + } +} diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index 5d3e366341..7fa53b66aa 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -26,6 +26,8 @@ import ConvertOpusToAAC import LocalAudioTranscription import TextSelectionNode import AudioTranscriptionPendingIndicatorComponent +import UndoUI +import TelegramNotices private struct FetchControls { let fetch: (Bool) -> Void @@ -348,7 +350,21 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } private func transcribe() { - guard let context = self.context, let message = self.message, let presentationData = self.presentationData else { + guard let arguments = self.arguments, let context = self.context, let message = self.message, let presentationData = self.presentationData else { + return + } + + guard arguments.associatedData.isPremium else { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let tipController = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_voiceToText", scale: 0.065, colors: [:], title: nil, text: presentationData.strings.Message_AudioTranscription_SubscribeToPremium, customUndoText: presentationData.strings.Message_AudioTranscription_SubscribeToPremiumAction), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { action in + if case .undo = action { + let introController = context.sharedContext.makePremiumIntroController(context: context, source: .settings) + arguments.controllerInteraction.navigationController()?.pushViewController(introController, animated: true) + + let _ = ApplicationSpecificNotice.incrementAudioTranscriptionSuggestion(accountManager: context.sharedContext.accountManager).start() + } + return false }) + arguments.controllerInteraction.presentControllerInCurrent(tipController, nil) return } @@ -550,7 +566,18 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { var isVoice = false var audioDuration: Int32 = 0 - let canTranscribe = arguments.associatedData.isPremium && arguments.message.id.peerId.namespace != Namespaces.Peer.SecretChat + let displayTranscribe: Bool + if arguments.message.id.peerId.namespace != Namespaces.Peer.SecretChat { + if arguments.associatedData.isPremium { + displayTranscribe = true + } else if arguments.associatedData.alwaysDisplayTranscribeButton { + displayTranscribe = true + } else { + displayTranscribe = false + } + } else { + displayTranscribe = false + } let messageTheme = arguments.incoming ? arguments.presentationData.theme.theme.chat.message.incoming : arguments.presentationData.theme.theme.chat.message.outgoing @@ -805,7 +832,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { let calcDuration = max(minVoiceLength, min(maxVoiceLength, CGFloat(audioDuration))) minLayoutWidth = minVoiceWidth + (maxVoiceWidth - minVoiceWidth) * (calcDuration - minVoiceLength) / (maxVoiceLength - minVoiceLength) - if canTranscribe { + if displayTranscribe { minLayoutWidth += 30.0 + 8.0 } minLayoutWidth = max(descriptionAndStatusWidth + 56, minLayoutWidth) @@ -1093,7 +1120,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { if isVoice { var scrubbingFrame = CGRect(origin: CGPoint(x: 57.0, y: 1.0), size: CGSize(width: boundingWidth - 60.0, height: 18.0)) - if canTranscribe { + if displayTranscribe { scrubbingFrame.size.width -= 30.0 + 4.0 } @@ -1152,7 +1179,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { animation.animator.updateFrame(layer: waveformView.layer, frame: scrubbingFrame, completion: nil) animation.animator.updateFrame(layer: waveformView.componentView!.layer, frame: CGRect(origin: CGPoint(), size: scrubbingFrame.size), completion: nil) - if canTranscribe { + if displayTranscribe { let audioTranscriptionButton: ComponentHostView if let current = strongSelf.audioTranscriptionButton { audioTranscriptionButton = current diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift index e52feba0ba..e2c4688367 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -15,6 +15,8 @@ import FileMediaResourceStatus import HierarchyTrackingLayer import ComponentFlow import AudioTranscriptionButtonComponent +import UndoUI +import TelegramNotices struct ChatMessageInstantVideoItemLayoutResult { let contentSize: CGSize @@ -584,8 +586,20 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { let incoming = item.message.effectivelyIncoming(item.context.account.peerId) - var canTranscribe = statusDisplayType == .free && item.associatedData.isPremium && item.message.id.peerId.namespace != Namespaces.Peer.SecretChat - if canTranscribe, let durationBlurColor = durationBlurColor { + var displayTranscribe: Bool + if item.message.id.peerId.namespace != Namespaces.Peer.SecretChat && statusDisplayType == .free { + if item.associatedData.isPremium { + displayTranscribe = true + } else if item.associatedData.alwaysDisplayTranscribeButton { + displayTranscribe = true + } else { + displayTranscribe = false + } + } else { + displayTranscribe = false + } + + if displayTranscribe, let durationBlurColor = durationBlurColor { let audioTranscriptionButton: ComponentHostView if let current = strongSelf.audioTranscriptionButton { audioTranscriptionButton = current @@ -627,7 +641,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { animation.animator.updateAlpha(layer: audioTranscriptionButton.layer, alpha: scaleProgress.isZero ? 1.0 : 0.0, completion: nil) if !scaleProgress.isZero { - canTranscribe = false + displayTranscribe = false } } else { if let audioTranscriptionButton = strongSelf.audioTranscriptionButton { @@ -644,7 +658,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { if let durationBackgroundNode = strongSelf.durationBackgroundNode, size.width > 1.0 { durationBackgroundNode.update(size: size, cornerRadius: size.height / 2.0, transition: .immediate) - if !incoming, let audioTranscriptionButton = strongSelf.audioTranscriptionButton, canTranscribe { + if !incoming, let audioTranscriptionButton = strongSelf.audioTranscriptionButton, displayTranscribe { durationFrame.origin.x = audioTranscriptionButton.frame.minX - 7.0 } animation.animator.updateFrame(layer: durationNode.layer, frame: durationFrame, completion: nil) @@ -662,14 +676,14 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { dateAndStatusOrigin = CGPoint(x: displayVideoFrame.minX - 4.0, y: displayVideoFrame.maxY + 2.0) } else { dateAndStatusOrigin = CGPoint(x: min(floorToScreenPixels(displayVideoFrame.midX) + 55.0 + 25.0 * scaleProgress, width - dateAndStatusSize.width - 4.0), y: displayVideoFrame.height - dateAndStatusSize.height) - if !incoming, let audioTranscriptionButton = strongSelf.audioTranscriptionButton, canTranscribe { + if !incoming, let audioTranscriptionButton = strongSelf.audioTranscriptionButton, displayTranscribe { dateAndStatusOrigin.x = audioTranscriptionButton.frame.maxX + 7.0 } } animation.animator.updateFrame(layer: strongSelf.dateAndStatusNode.layer, frame: CGRect(origin: dateAndStatusOrigin, size: dateAndStatusSize), completion: nil) case let .constrained(_, right): var dateAndStatusFrame = CGRect(origin: CGPoint(x: min(floorToScreenPixels(displayVideoFrame.midX) + 55.0 + 25.0 * scaleProgress, displayVideoFrame.maxX + right - dateAndStatusSize.width - 4.0), y: displayVideoFrame.maxY - dateAndStatusSize.height), size: dateAndStatusSize) - if incoming, let audioTranscriptionButton = strongSelf.audioTranscriptionButton, canTranscribe { + if incoming, let audioTranscriptionButton = strongSelf.audioTranscriptionButton, displayTranscribe { dateAndStatusFrame.origin.x = audioTranscriptionButton.frame.maxX + 7.0 } animation.animator.updateFrame(layer: strongSelf.dateAndStatusNode.layer, frame: dateAndStatusFrame, completion: nil) @@ -1164,7 +1178,21 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } private func transcribe() { - guard let context = self.item?.context, let message = self.item?.message, self.statusNode == nil else { + guard let item = self.item, self.statusNode == nil else { + return + } + + guard item.associatedData.isPremium else { + let presentationData = item.context.sharedContext.currentPresentationData.with { $0 } + let tipController = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_voiceToText", scale: 0.065, colors: [:], title: nil, text: presentationData.strings.Message_AudioTranscription_SubscribeToPremium, customUndoText: presentationData.strings.Message_AudioTranscription_SubscribeToPremiumAction), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { action in + if case .undo = action { + let introController = item.context.sharedContext.makePremiumIntroController(context: item.context, source: .settings) + item.controllerInteraction.navigationController()?.pushViewController(introController, animated: true) + + let _ = ApplicationSpecificNotice.incrementAudioTranscriptionSuggestion(accountManager: item.context.sharedContext.accountManager).start() + } + return false }) + item.controllerInteraction.presentControllerInCurrent(tipController, nil) return } @@ -1174,7 +1202,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { if case .expanded = self.audioTranscriptionState { shouldExpandNow = true } else { - if let result = transcribedText(message: message) { + if let result = transcribedText(message: item.message) { shouldExpandNow = true if case let .success(_, isPending) = result { @@ -1192,7 +1220,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { self.audioTranscriptionState = .inProgress self.requestUpdateLayout(true) - self.transcribeDisposable = (context.engine.messages.transcribeAudio(messageId: message.id) + self.transcribeDisposable = (item.context.engine.messages.transcribeAudio(messageId: item.message.id) |> deliverOnMainQueue).start(next: { [weak self] result in guard let strongSelf = self else { return diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 680c79e20d..a848eae004 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1520,6 +1520,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSource = .profile(peerId) case let .emojiStatus(peerId, fileId, file, packTitle): mappedSource = .emojiStatus(peerId, fileId, file, packTitle) + case .voiceToText: + mappedSource = .voiceToText } return PremiumIntroScreen(context: context, source: mappedSource) } diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index f3b2a3cc15..65cf33709b 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -1337,6 +1337,8 @@ public func themeIconImage(account: Account, accountManager: AccountManager