mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-06-20 18:24:43 +00:00
Merge commit 'e3d4c2e881d8c35014edc92148cc2649e0c48597'
# Conflicts: # submodules/AccountContext/Sources/ChatController.swift # submodules/TelegramUI/Sources/ChatHistoryListNode.swift
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -793,6 +793,7 @@ public enum PremiumIntroSource {
|
||||
case deeplink(String?)
|
||||
case profile(PeerId)
|
||||
case emojiStatus(PeerId, Int64, TelegramMediaFile?, LoadedStickerPack?)
|
||||
case voiceToText
|
||||
}
|
||||
|
||||
#if ENABLE_WALLET
|
||||
|
||||
@@ -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<EnginePeer.Id> = 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<EnginePeer.Id> = 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -185,7 +185,7 @@ func _internal_deactivateAllAddressNames(account: Account, peerId: EnginePeer.Id
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Never, DeactivateAllAddressNamesError> in
|
||||
return account.postbox.transaction { transaction -> Signal<Void, DeactivateAllAddressNamesError> 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
|
||||
|
||||
@@ -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<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> {
|
||||
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<TelegramAccountManagerTypes>, count: Int = 1) -> Signal<Int, NoError> {
|
||||
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<TelegramAccountManagerTypes>) -> Signal<Void, NoError> {
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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<PeerId> = 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<EnginePeer.Id?, NoError>
|
||||
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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Empty>
|
||||
if let current = strongSelf.audioTranscriptionButton {
|
||||
audioTranscriptionButton = current
|
||||
|
||||
@@ -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<Empty>
|
||||
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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -1337,6 +1337,8 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
|
||||
var defaultTheme = makeDefaultPresentationTheme(reference: theme, serviceBackgroundColor: nil)
|
||||
if let color = color {
|
||||
defaultTheme = customizePresentationTheme(defaultTheme, editing: false, accentColor: color.accentColor.flatMap { UIColor(rgb: $0) }, outgoingAccentColor: nil, backgroundColors: [], bubbleColors: color.bubbleColors, animateBubbleColors: nil, baseColor: color.baseColor)
|
||||
} else if case .night = theme {
|
||||
defaultTheme = customizePresentationTheme(defaultTheme, editing: true, accentColor: UIColor(rgb: 0x3e88f7), outgoingAccentColor: nil, backgroundColors: [], bubbleColors: [], animateBubbleColors: nil)
|
||||
}
|
||||
themeSignal = .single(defaultTheme)
|
||||
} else if case let .cloud(theme) = theme, let settings = theme.theme.settings?.first {
|
||||
|
||||
Reference in New Issue
Block a user