From f02722c9549393088070019266c1d23404dc49be Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 4 Jan 2024 17:15:12 +0400 Subject: [PATCH] Various fixes --- .../Telegram-iOS/en.lproj/Localizable.strings | 3 + .../AccountContext/Sources/MediaManager.swift | 2 + .../Sources/AttachmentPanel.swift | 1 + .../ChatPanelInterfaceInteraction.swift | 5 + .../Sources/ImageObjectSeparation.swift | 2 +- .../Sources/InAppPurchaseManager.swift | 23 +- .../TGModernConversationInputMicButton.m | 37 ++-- .../Sources/MessageContentKind.swift | 24 +++ .../ChatMessageContactBubbleContentNode/BUILD | 1 + .../ChatMessageContactBubbleContentNode.swift | 158 ++++++++++++-- .../Sources/ChatRecentActionsController.swift | 1 + .../Sources/MediaEditorScreen.swift | 9 +- .../Sources/PeerInfoScreen.swift | 3 +- .../Sources/PeerSelectionControllerNode.swift | 1 + .../Images.xcassets/Chat/Input/Contents.json | 6 +- .../Chat/Input/Recording/Contents.json | 9 + .../Recording/Pause.imageset/Contents.json | 12 ++ .../Pause.imageset/pausevoicecideo_30.pdf | 97 +++++++++ .../SwitchCamera.imageset/Contents.json | 12 ++ .../SwitchCamera.imageset/switchcamera_30.pdf | 188 +++++++++++++++++ .../TelegramUI/Sources/ChatController.swift | 199 ++++++++++-------- .../Sources/ChatHistoryEntriesForView.swift | 5 + .../Sources/ChatHistoryListNode.swift | 1 + .../ChatInterfaceStateContextMenus.swift | 2 +- .../ChatRecordingPreviewInputPanelNode.swift | 49 ++++- .../Sources/ChatTextInputPanelNode.swift | 4 +- .../Sources/ManagedAudioRecorder.swift | 32 ++- 27 files changed, 732 insertions(+), 154 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/Pause.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/Pause.imageset/pausevoicecideo_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/SwitchCamera.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/SwitchCamera.imageset/switchcamera_30.pdf diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 7b396c5bc7..a2669e9ca9 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -10867,3 +10867,6 @@ Sorry for the inconvenience."; "Chat.PlayOnceMesasgeClose" = "Close"; "Chat.PlayOnceMesasgeCloseAndDelete" = "Close and Delete"; "Chat.PlayOnceMesasge.DisableScreenCapture" = "Sorry, you can't play this message while screen recording is active."; + +"Conversation.ContactAddContact" = "ADD"; +"Conversation.ContactMessage" = "MESSAGE"; diff --git a/submodules/AccountContext/Sources/MediaManager.swift b/submodules/AccountContext/Sources/MediaManager.swift index 6ca9d90f50..02cda6c109 100644 --- a/submodules/AccountContext/Sources/MediaManager.swift +++ b/submodules/AccountContext/Sources/MediaManager.swift @@ -228,6 +228,8 @@ public protocol ManagedAudioRecorder: AnyObject { var recordingState: Signal { get } func start() + func pause() + func resume() func stop() func takenRecordedData() -> Signal } diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index ee7d84d87d..d443d2e836 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -826,6 +826,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { }, finishMediaRecording: { _ in }, stopMediaRecording: { }, lockMediaRecording: { + }, resumeMediaRecording: { }, deleteRecordedMedia: { }, sendRecordedMedia: { _, _ in }, displayRestrictedInfo: { _, _ in diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift index dc0e5e9e51..fb1fd2e668 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift @@ -17,6 +17,7 @@ public enum ChatLoadingMessageSubject { public enum ChatFinishMediaRecordingAction { case dismiss case preview + case pause case send(viewOnce: Bool) } @@ -108,6 +109,7 @@ public final class ChatPanelInterfaceInteraction { public let finishMediaRecording: (ChatFinishMediaRecordingAction) -> Void public let stopMediaRecording: () -> Void public let lockMediaRecording: () -> Void + public let resumeMediaRecording: () -> Void public let deleteRecordedMedia: () -> Void public let sendRecordedMedia: (Bool, Bool) -> Void public let displayRestrictedInfo: (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void @@ -214,6 +216,7 @@ public final class ChatPanelInterfaceInteraction { finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void, stopMediaRecording: @escaping () -> Void, lockMediaRecording: @escaping () -> Void, + resumeMediaRecording: @escaping () -> Void, deleteRecordedMedia: @escaping () -> Void, sendRecordedMedia: @escaping (Bool, Bool) -> Void, displayRestrictedInfo: @escaping (ChatPanelRestrictionInfoSubject, ChatPanelRestrictionInfoDisplayType) -> Void, @@ -319,6 +322,7 @@ public final class ChatPanelInterfaceInteraction { self.finishMediaRecording = finishMediaRecording self.stopMediaRecording = stopMediaRecording self.lockMediaRecording = lockMediaRecording + self.resumeMediaRecording = resumeMediaRecording self.deleteRecordedMedia = deleteRecordedMedia self.sendRecordedMedia = sendRecordedMedia self.displayRestrictedInfo = displayRestrictedInfo @@ -431,6 +435,7 @@ public final class ChatPanelInterfaceInteraction { }, finishMediaRecording: { _ in }, stopMediaRecording: { }, lockMediaRecording: { + }, resumeMediaRecording: { }, deleteRecordedMedia: { }, sendRecordedMedia: { _, _ in }, displayRestrictedInfo: { _, _ in diff --git a/submodules/DrawingUI/Sources/ImageObjectSeparation.swift b/submodules/DrawingUI/Sources/ImageObjectSeparation.swift index c27efa81de..764975d4f6 100644 --- a/submodules/DrawingUI/Sources/ImageObjectSeparation.swift +++ b/submodules/DrawingUI/Sources/ImageObjectSeparation.swift @@ -8,7 +8,7 @@ import VideoToolbox private let queue = Queue() -func cutoutStickerImage(from image: UIImage) -> Signal { +public func cutoutStickerImage(from image: UIImage) -> Signal { if #available(iOS 17.0, *) { guard let cgImage = image.cgImage else { return .single(nil) diff --git a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift index 6882ac5c79..fee9d00c8b 100644 --- a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift +++ b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift @@ -440,7 +440,6 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver { } |> take(1) - let product: Signal = products |> map { products in if let product = products.first(where: { $0.id == productIdentifier }) { @@ -469,19 +468,12 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver { } } } + completion = updatePendingInAppPurchaseState(engine: self.engine, productId: productIdentifier, content: nil) let receiptData = getReceiptData() ?? Data() - #if DEBUG - let id = Int64.random(in: Int64.min ... Int64.max) - let fileResource = LocalFileMediaResource(fileId: id, size: Int64(receiptData.count), isSecretRelated: false) - self.engine.account.postbox.mediaBox.storeResourceData(fileResource.id, data: receiptData) - - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(receiptData.count), attributes: [.FileName(fileName: "Receipt.dat")]) - let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) - - let _ = enqueueMessages(account: self.engine.account, peerId: self.engine.account.peerId, messages: [message]).start() + self.debugSaveReceipt(receiptData: receiptData) #endif self.disposableSet.set( @@ -553,6 +545,17 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver { } } } + + private func debugSaveReceipt(receiptData: Data) { + let id = Int64.random(in: Int64.min ... Int64.max) + let fileResource = LocalFileMediaResource(fileId: id, size: Int64(receiptData.count), isSecretRelated: false) + self.engine.account.postbox.mediaBox.storeResourceData(fileResource.id, data: receiptData) + + let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(receiptData.count), attributes: [.FileName(fileName: "Receipt.dat")]) + let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) + + let _ = enqueueMessages(account: self.engine.account, peerId: self.engine.account.peerId, messages: [message]).start() + } } private final class PendingInAppPurchaseState: Codable { diff --git a/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m b/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m index 30cf93a7bc..e388791013 100644 --- a/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m +++ b/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m @@ -317,24 +317,31 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius CGContextSetStrokeColorWithColor(context, (self.pallete != nil ? self.pallete.borderColor : UIColorRGB(0xb2b2b2)).CGColor); CGContextSetLineWidth(context, TGScreenPixel); - CGRect rect1 = CGRectMake(TGScreenPixel / 2.0f, TGScreenPixel / 2.0f, 40.0f - TGScreenPixel, 40.0 - TGScreenPixel); - CGContextFillEllipseInRect(context, rect1); - CGContextStrokeEllipseInRect(context, rect1); - CGRect iconRect = CGRectInset(rect1, 12.0f, 12.0f); - CGFloat radius = 1.0f; + CGContextAddPath(context, [UIBezierPath bezierPathWithRoundedRect:CGRectMake(12.0, 12.0, 5.0, 14.0) cornerRadius:1.0].CGPath); + CGContextFillPath(context); - CGFloat minx = CGRectGetMinX(iconRect), midx = CGRectGetMidX(iconRect), maxx = CGRectGetMaxX(iconRect); - CGFloat miny = CGRectGetMinY(iconRect), midy = CGRectGetMidY(iconRect), maxy = CGRectGetMaxY(iconRect); + CGContextAddPath(context, [UIBezierPath bezierPathWithRoundedRect:CGRectMake(40.0 - 12.0 - 5.0, 12.0, 5.0, 14.0) cornerRadius:1.0].CGPath); + CGContextFillPath(context); - CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor); - - CGContextMoveToPoint(context, minx, midy); - CGContextAddArcToPoint(context, minx, miny, midx, miny, radius); - CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius); - CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius); - CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius); - CGContextClosePath(context); +// CGRect rect1 = CGRectMake(TGScreenPixel / 2.0f, TGScreenPixel / 2.0f, 40.0f - TGScreenPixel, 40.0 - TGScreenPixel); +// CGContextFillEllipseInRect(context, rect1); +// CGContextStrokeEllipseInRect(context, rect1); +// +// CGRect iconRect = CGRectInset(rect1, 12.0f, 12.0f); +// CGFloat radius = 1.0f; +// +// CGFloat minx = CGRectGetMinX(iconRect), midx = CGRectGetMidX(iconRect), maxx = CGRectGetMaxX(iconRect); +// CGFloat miny = CGRectGetMinY(iconRect), midy = CGRectGetMidY(iconRect), maxy = CGRectGetMaxY(iconRect); +// +// CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor); +// +// CGContextMoveToPoint(context, minx, midy); +// CGContextAddArcToPoint(context, minx, miny, midx, miny, radius); +// CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius); +// CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius); +// CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius); +// CGContextClosePath(context); CGContextSetFillColorWithColor(context, (self.pallete != nil ? self.pallete.buttonColor : TGAccentColor()).CGColor); CGContextDrawPath(context, kCGPathFill); diff --git a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift index 97b7b3d924..7b445965c6 100644 --- a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift +++ b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift @@ -21,6 +21,8 @@ public enum MessageContentKindKey { case liveLocation case expiredImage case expiredVideo + case expiredVoiceMessage + case expiredVideoMessage case poll case restricted case dice @@ -44,6 +46,8 @@ public enum MessageContentKind: Equatable { case liveLocation case expiredImage case expiredVideo + case expiredVoiceMessage + case expiredVideoMessage case poll(String) case restricted(String) case dice(String) @@ -137,6 +141,18 @@ public enum MessageContentKind: Equatable { } else { return false } + case .expiredVoiceMessage: + if case .expiredVoiceMessage = other { + return true + } else { + return false + } + case .expiredVideoMessage: + if case .expiredVideoMessage = other { + return true + } else { + return false + } case .poll: if case .poll = other { return true @@ -206,6 +222,10 @@ public enum MessageContentKind: Equatable { return .expiredImage case .expiredVideo: return .expiredVideo + case .expiredVoiceMessage: + return .expiredVoiceMessage + case .expiredVideoMessage: + return .expiredVideoMessage case .poll: return .poll case .restricted: @@ -405,6 +425,10 @@ public func stringForMediaKind(_ kind: MessageContentKind, strings: Presentation return (NSAttributedString(string: strings.Message_ImageExpired), true) case .expiredVideo: return (NSAttributedString(string: strings.Message_VideoExpired), true) + case .expiredVoiceMessage: + return (NSAttributedString(string: strings.Message_VoiceMessageExpired), true) + case .expiredVideoMessage: + return (NSAttributedString(string: strings.Message_VideoMessageExpired), true) case let .poll(text): return (NSAttributedString(string: "📊 \(text)"), false) case let .restricted(text): diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/BUILD index 66ab105003..ce75d53b14 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/BUILD @@ -24,6 +24,7 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", "//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode", "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift index 8d87a3e7f7..a08014ab8f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift @@ -14,13 +14,17 @@ import ChatMessageBubbleContentNode import ChatMessageItemCommon import ChatMessageAttachedContentButtonNode import ChatControllerInteraction +import MessageInlineBlockBackgroundView private let avatarFont = avatarPlaceholderFont(size: 16.0) -private let titleFont = Font.medium(14.0) +private let titleFont = Font.semibold(14.0) private let textFont = Font.regular(14.0) public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { + private var backgroundView: MessageInlineBlockBackgroundView? + private var actionButtonSeparator: SimpleLayer? + private let avatarNode: AvatarNode private let dateAndStatusNode: ChatMessageDateAndStatusNode private let titleNode: TextNode @@ -29,23 +33,27 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { private var contact: TelegramMediaContact? private var contactInfo : String? - private let buttonNode: ChatMessageAttachedContentButtonNode + private let addButtonNode: ChatMessageAttachedContentButtonNode + private let messageButtonNode: ChatMessageAttachedContentButtonNode required public init() { self.avatarNode = AvatarNode(font: avatarFont) self.dateAndStatusNode = ChatMessageDateAndStatusNode() self.titleNode = TextNode() self.textNode = TextNode() - self.buttonNode = ChatMessageAttachedContentButtonNode() + self.addButtonNode = ChatMessageAttachedContentButtonNode() + self.messageButtonNode = ChatMessageAttachedContentButtonNode() super.init() self.addSubnode(self.avatarNode) self.addSubnode(self.titleNode) self.addSubnode(self.textNode) - self.addSubnode(self.buttonNode) + self.addSubnode(self.addButtonNode) + self.addSubnode(self.messageButtonNode) - self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + self.addButtonNode.addTarget(self, action: #selector(self.addButtonPressed), forControlEvents: .touchUpInside) + self.messageButtonNode.addTarget(self, action: #selector(self.messageButtonPressed), forControlEvents: .touchUpInside) self.dateAndStatusNode.reactionSelected = { [weak self] value in guard let strongSelf = self, let item = strongSelf.item else { @@ -65,7 +73,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { } override public func accessibilityActivate() -> Bool { - self.buttonPressed() + self.addButtonPressed() return true } @@ -84,7 +92,8 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { let statusLayout = self.dateAndStatusNode.asyncLayout() let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTextLayout = TextNode.asyncLayout(self.textNode) - let makeButtonLayout = ChatMessageAttachedContentButtonNode.asyncLayout(self.buttonNode) + let makeMessageButtonLayout = ChatMessageAttachedContentButtonNode.asyncLayout(self.messageButtonNode) + let makeAddButtonLayout = ChatMessageAttachedContentButtonNode.asyncLayout(self.addButtonNode) let previousContact = self.contact let previousContactInfo = self.contactInfo @@ -102,6 +111,39 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { incoming = false } + + var contactPeer: Peer? + if let peerId = selectedContact?.peerId, let peer = item.message.peers[peerId] { + contactPeer = peer + } + + let nameColors = contactPeer?.nameColor.flatMap { item.context.peerNameColors.get($0, dark: item.presentationData.theme.theme.overallDarkAppearance) } + + let messageTheme = incoming ? item.presentationData.theme.theme.chat.message.incoming : item.presentationData.theme.theme.chat.message.outgoing + let mainColor: UIColor + var secondaryColor: UIColor? + var tertiaryColor: UIColor? + if !incoming { + mainColor = messageTheme.accentTextColor + if let _ = nameColors?.secondary { + secondaryColor = .clear + } + if let _ = nameColors?.tertiary { + tertiaryColor = .clear + } + } else { + var authorNameColor: UIColor? + authorNameColor = nameColors?.main + secondaryColor = nameColors?.secondary + tertiaryColor = nameColors?.tertiary + + if let authorNameColor { + mainColor = authorNameColor + } else { + mainColor = messageTheme.accentTextColor + } + } + var titleString: NSAttributedString? var textString: NSAttributedString? var updatedContactInfo: String? @@ -159,8 +201,8 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { updatedContactInfo = info - titleString = NSAttributedString(string: displayName, font: titleFont, textColor: incoming ? item.presentationData.theme.theme.chat.message.incoming.accentTextColor : item.presentationData.theme.theme.chat.message.outgoing.accentTextColor) - textString = NSAttributedString(string: info, font: textFont, textColor: incoming ? item.presentationData.theme.theme.chat.message.incoming.primaryTextColor : item.presentationData.theme.theme.chat.message.outgoing.primaryTextColor) + titleString = NSAttributedString(string: displayName, font: titleFont, textColor: mainColor) + textString = NSAttributedString(string: info, font: textFont, textColor: messageTheme.primaryTextColor) } else { updatedContactInfo = nil } @@ -247,42 +289,47 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { )) } - let titleColor: UIColor let avatarPlaceholderColor: UIColor if incoming { - titleColor = item.presentationData.theme.theme.chat.message.incoming.accentTextColor avatarPlaceholderColor = item.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor } else { - titleColor = item.presentationData.theme.theme.chat.message.outgoing.accentTextColor avatarPlaceholderColor = item.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor } - let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, nil, false, item.presentationData.strings.Conversation_ViewContactDetails, titleColor, false, true) + let (messageButtonWidth, messageContinueLayout) = makeMessageButtonLayout(constrainedSize.width, nil, false, item.presentationData.strings.Conversation_ContactMessage.uppercased(), mainColor, false, false) + let (addButtonWidth, addContinueLayout) = makeAddButtonLayout(constrainedSize.width, nil, false, item.presentationData.strings.Conversation_ContactAddContact.uppercased(), mainColor, false, false) + let maxButtonWidth = max(messageButtonWidth, addButtonWidth) var maxContentWidth: CGFloat = avatarSize.width + 7.0 if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue { maxContentWidth = max(maxContentWidth, statusSuggestedWidthAndContinue.0) } maxContentWidth = max(maxContentWidth, avatarSize.width + 7.0 + titleLayout.size.width) maxContentWidth = max(maxContentWidth, avatarSize.width + 7.0 + textLayout.size.width) - maxContentWidth = max(maxContentWidth, buttonWidth) + maxContentWidth = max(maxContentWidth, maxButtonWidth * 2.0) + maxContentWidth = max(maxContentWidth, 240.0) let contentWidth = maxContentWidth + layoutConstants.text.bubbleInsets.right * 2.0 return (contentWidth, { boundingWidth in let baseAvatarFrame = CGRect(origin: CGPoint(x: layoutConstants.text.bubbleInsets.right, y: layoutConstants.text.bubbleInsets.top), size: avatarSize) - let (buttonSize, buttonApply) = continueLayout(boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0, 33.0) + let lineWidth: CGFloat = 3.0 + let buttonWidth = floor((boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0 - lineWidth) / 2.0) + let (messageButtonSize, messageButtonApply) = messageContinueLayout(buttonWidth, 33.0) + let (addButtonSize, addButtonApply) = addContinueLayout(buttonWidth, 33.0) + let buttonSpacing: CGFloat = 4.0 let statusSizeAndApply = statusSuggestedWidthAndContinue?.1(boundingWidth - sideInsets) - var layoutSize = CGSize(width: contentWidth, height: 49.0 + textLayout.size.height + buttonSize.height + buttonSpacing) + var layoutSize = CGSize(width: contentWidth, height: 64.0 + textLayout.size.height + addButtonSize.height + buttonSpacing) if let statusSizeAndApply = statusSizeAndApply { layoutSize.height += statusSizeAndApply.0.height - 4.0 } - let buttonFrame = CGRect(origin: CGPoint(x: layoutConstants.text.bubbleInsets.right, y: layoutSize.height - 9.0 - buttonSize.height), size: buttonSize) - let avatarFrame = baseAvatarFrame.offsetBy(dx: 0.0, dy: 5.0) + let messageButtonFrame = CGRect(origin: CGPoint(x: layoutConstants.text.bubbleInsets.right + lineWidth, y: layoutSize.height - 24.0 - messageButtonSize.height), size: messageButtonSize) + let addButtonFrame = CGRect(origin: CGPoint(x: layoutConstants.text.bubbleInsets.right + lineWidth + buttonWidth, y: layoutSize.height - 24.0 - addButtonSize.height), size: addButtonSize) + let avatarFrame = baseAvatarFrame.offsetBy(dx: 9.0, dy: 14.0) var customLetters: [String] = [] if let selectedContact = selectedContact, selectedContact.peerId == nil { @@ -309,14 +356,20 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { let _ = titleApply() let _ = textApply() - let _ = buttonApply(animation) + let _ = messageButtonApply(animation) + let _ = addButtonApply(animation) strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: avatarFrame.maxX + 7.0, y: avatarFrame.minY + 1.0), size: titleLayout.size) strongSelf.textNode.frame = CGRect(origin: CGPoint(x: avatarFrame.maxX + 7.0, y: avatarFrame.minY + 20.0), size: textLayout.size) - strongSelf.buttonNode.frame = buttonFrame + strongSelf.addButtonNode.frame = addButtonFrame + + strongSelf.messageButtonNode.frame = messageButtonFrame + + let backgroundInsets = layoutConstants.text.bubbleInsets + let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top + 5.0), size: CGSize(width: contentWidth - layoutConstants.text.bubbleInsets.right * 2.0, height: 94.0)) if let statusSizeAndApply = statusSizeAndApply { - strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: layoutConstants.text.bubbleInsets.left, y: strongSelf.textNode.frame.maxY + 2.0), size: statusSizeAndApply.0) + strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: layoutConstants.text.bubbleInsets.left, y: backgroundFrame.maxY + 3.0), size: statusSizeAndApply.0) if strongSelf.dateAndStatusNode.supernode == nil { strongSelf.addSubnode(strongSelf.dateAndStatusNode) statusSizeAndApply.1(.None) @@ -359,6 +412,48 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { } else { strongSelf.dateAndStatusNode.pressed = nil } + + var pattern: MessageInlineBlockBackgroundView.Pattern? + if let contactPeer, let backgroundEmojiId = contactPeer.backgroundEmojiId { + pattern = MessageInlineBlockBackgroundView.Pattern( + context: item.context, + fileId: backgroundEmojiId, + file: item.message.associatedMedia[MediaId( + namespace: Namespaces.Media.CloudFile, + id: backgroundEmojiId + )] as? TelegramMediaFile + ) + } + + let patternTopRightPosition = CGPoint() + + let backgroundView: MessageInlineBlockBackgroundView + if let current = strongSelf.backgroundView { + backgroundView = current + animation.animator.updateFrame(layer: backgroundView.layer, frame: backgroundFrame, completion: nil) + backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, backgroundColor: nil, pattern: pattern, patternTopRightPosition: patternTopRightPosition, animation: animation) + } else { + backgroundView = MessageInlineBlockBackgroundView() + strongSelf.backgroundView = backgroundView + backgroundView.frame = backgroundFrame + strongSelf.view.insertSubview(backgroundView, at: 0) + backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, backgroundColor: nil, pattern: pattern, patternTopRightPosition: patternTopRightPosition, animation: .None) + } + + let separatorFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + 9.0, y: backgroundFrame.maxY - 36.0), size: CGSize(width: backgroundFrame.width - 18.0, height: UIScreenPixel)) + + let actionButtonSeparator: SimpleLayer + if let current = strongSelf.actionButtonSeparator { + actionButtonSeparator = current + animation.animator.updateFrame(layer: actionButtonSeparator, frame: separatorFrame, completion: nil) + } else { + actionButtonSeparator = SimpleLayer() + strongSelf.actionButtonSeparator = actionButtonSeparator + strongSelf.layer.addSublayer(actionButtonSeparator) + actionButtonSeparator.frame = separatorFrame + } + + actionButtonSeparator.backgroundColor = mainColor.withMultipliedAlpha(0.2).cgColor } }) }) @@ -379,7 +474,10 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { } override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { - if self.buttonNode.frame.contains(point) { + if self.messageButtonNode.frame.contains(point) { + return ChatMessageBubbleContentTapAction(content: .ignore) + } + if self.addButtonNode.frame.contains(point) { return ChatMessageBubbleContentTapAction(content: .openMessage) } if self.dateAndStatusNode.supernode != nil, let _ = self.dateAndStatusNode.hitTest(self.view.convert(point, to: self.dateAndStatusNode.view), with: nil) { @@ -396,12 +494,26 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { } } - @objc private func buttonPressed() { + @objc private func addButtonPressed() { if let item = self.item { let _ = item.controllerInteraction.openMessage(item.message, OpenMessageParams(mode: .default)) } } + @objc private func messageButtonPressed() { + if let item = self.item { + var selectedContact: TelegramMediaContact? + for media in item.message.media { + if let media = media as? TelegramMediaContact { + selectedContact = media + } + } + if let peerId = selectedContact?.peerId, let peer = item.message.peers[peerId] { + item.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default) + } + } + } + override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { if !self.dateAndStatusNode.isHidden { return self.dateAndStatusNode.reactionView(value: value) diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift index 39fa03c8e8..5aebf2bbff 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift @@ -102,6 +102,7 @@ public final class ChatRecentActionsController: TelegramBaseController { }, finishMediaRecording: { _ in }, stopMediaRecording: { }, lockMediaRecording: { + }, resumeMediaRecording: { }, deleteRecordedMedia: { }, sendRecordedMedia: { _, _ in }, displayRestrictedInfo: { _, _ in diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 1ce7002286..c8ed2ae305 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -2473,7 +2473,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate messageEntity.secondaryRenderImage = result.nightImage messageEntity.overlayRenderImage = result.overlayImage messageEntity.referenceDrawingSize = storyDimensions - messageEntity.position = CGPoint(x: storyDimensions.width / 2.0 - 16.0, y: storyDimensions.height / 2.0) + messageEntity.position = CGPoint(x: storyDimensions.width / 2.0 - 54.0, y: storyDimensions.height / 2.0) let fraction = max(result.size.width, result.size.height) / 353.0 messageEntity.scale = min(6.0, 3.3 * fraction) @@ -3375,6 +3375,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate let entity = DrawingStickerEntity(content: .image(updatedImage, .rectangle)) entity.canCutOut = false + let _ = (cutoutStickerImage(from: image) + |> deliverOnMainQueue).start(next: { [weak entity] result in + if result != nil, let entity { + entity.canCutOut = true + } + }) + self?.interaction?.insertEntity(entity, scale: 2.5) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 6490b6d319..03747f2e78 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -347,6 +347,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, finishMediaRecording: { _ in }, stopMediaRecording: { }, lockMediaRecording: { + }, resumeMediaRecording: { }, deleteRecordedMedia: { }, sendRecordedMedia: { _, _ in }, displayRestrictedInfo: { _, _ in @@ -2589,7 +2590,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if message.isCopyProtected() { - } else if message.id.peerId.namespace != Namespaces.Peer.SecretChat { + } else if message.id.peerId.namespace != Namespaces.Peer.SecretChat && message.minAutoremoveOrClearTimeout == nil { items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { c, _ in c.dismiss(completion: { if let strongSelf = self { diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index 445edfc04d..9fa9ca90dd 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -591,6 +591,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { }, finishMediaRecording: { _ in }, stopMediaRecording: { }, lockMediaRecording: { + }, resumeMediaRecording: { }, deleteRecordedMedia: { }, sendRecordedMedia: { _, _ in }, displayRestrictedInfo: { _, _ in diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Contents.json index 38f0c81fc2..6e965652df 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Input/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Contents.json @@ -1,9 +1,9 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 }, "properties" : { "provides-namespace" : true } -} \ No newline at end of file +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/Contents.json new file mode 100644 index 0000000000..6e965652df --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/Pause.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/Pause.imageset/Contents.json new file mode 100644 index 0000000000..93ddaefdce --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/Pause.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "pausevoicecideo_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/Pause.imageset/pausevoicecideo_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/Pause.imageset/pausevoicecideo_30.pdf new file mode 100644 index 0000000000..7430ec0055 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/Pause.imageset/pausevoicecideo_30.pdf @@ -0,0 +1,97 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 8.000000 8.000000 cm +0.000000 0.000000 0.000000 scn +0.038429 12.390181 m +0.000000 12.196983 0.000000 11.964655 0.000000 11.500000 c +0.000000 2.500000 l +0.000000 2.035345 0.000000 1.803018 0.038429 1.609819 c +0.196243 0.816438 0.816438 0.196242 1.609819 0.038429 c +1.803017 0.000000 2.035345 0.000000 2.500000 0.000000 c +2.964655 0.000000 3.196983 0.000000 3.390181 0.038429 c +4.183562 0.196242 4.803757 0.816438 4.961571 1.609819 c +5.000000 1.803018 5.000000 2.035345 5.000000 2.500000 c +5.000000 11.500000 l +5.000000 11.964655 5.000000 12.196983 4.961571 12.390181 c +4.803757 13.183562 4.183562 13.803758 3.390181 13.961571 c +3.196983 14.000000 2.964655 14.000000 2.500000 14.000000 c +2.035345 14.000000 1.803017 14.000000 1.609819 13.961571 c +0.816438 13.803758 0.196243 13.183562 0.038429 12.390181 c +h +9.038429 12.390181 m +9.000000 12.196983 9.000000 11.964655 9.000000 11.500000 c +9.000000 2.500000 l +9.000000 2.035345 9.000000 1.803018 9.038429 1.609819 c +9.196242 0.816438 9.816438 0.196242 10.609819 0.038429 c +10.803018 0.000000 11.035345 0.000000 11.500000 0.000000 c +11.964655 0.000000 12.196982 0.000000 12.390181 0.038429 c +13.183562 0.196242 13.803758 0.816438 13.961571 1.609819 c +14.000000 1.803018 14.000000 2.035345 14.000000 2.500000 c +14.000000 11.500000 l +14.000000 11.964655 14.000000 12.196983 13.961571 12.390181 c +13.803758 13.183562 13.183562 13.803758 12.390181 13.961571 c +12.196982 14.000000 11.964655 14.000000 11.500000 14.000000 c +11.035345 14.000000 10.803018 14.000000 10.609819 13.961571 c +9.816438 13.803758 9.196242 13.183562 9.038429 12.390181 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1660 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001750 00000 n +0000001773 00000 n +0000001946 00000 n +0000002020 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2079 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/SwitchCamera.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/SwitchCamera.imageset/Contents.json new file mode 100644 index 0000000000..c55a51057a --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/SwitchCamera.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "switchcamera_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/SwitchCamera.imageset/switchcamera_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/SwitchCamera.imageset/switchcamera_30.pdf new file mode 100644 index 0000000000..aa86094918 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Recording/SwitchCamera.imageset/switchcamera_30.pdf @@ -0,0 +1,188 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 3.084961 5.584656 cm +0.000000 0.000000 0.000000 scn +9.490515 20.080341 m +9.422689 20.080362 l +8.999260 20.080563 8.679590 20.080715 8.371326 20.006708 c +8.099400 19.941423 7.839443 19.833746 7.600999 19.687628 c +7.330693 19.521984 7.104758 19.295835 6.805490 18.996283 c +6.757546 18.948309 l +5.959852 18.150616 l +5.772345 17.963108 5.716480 17.908821 5.660614 17.864992 c +5.468593 17.714350 5.238950 17.619228 4.996650 17.589970 c +4.926156 17.581457 4.848266 17.580341 4.583089 17.580341 c +4.484859 17.580357 l +3.725908 17.580544 3.223788 17.580666 2.792615 17.474993 c +1.466152 17.149897 0.430476 16.114222 0.105380 14.787757 c +-0.000294 14.356585 -0.000171 13.854465 0.000015 13.095512 c +0.000031 12.997284 l +0.000031 5.465342 l +0.000031 5.436520 l +0.000025 4.620880 0.000020 3.968216 0.043112 3.440796 c +0.087345 2.899416 0.180274 2.431705 0.399492 2.001467 c +0.750868 1.311853 1.311542 0.751179 2.001156 0.399803 c +2.431395 0.180586 2.899104 0.087656 3.440485 0.043423 c +3.967894 0.000332 4.620543 0.000336 5.436161 0.000341 c +5.436225 0.000341 l +5.465031 0.000341 l +18.365032 0.000341 l +18.393837 0.000341 l +18.393900 0.000341 l +19.209520 0.000336 19.862169 0.000332 20.389578 0.043423 c +20.930958 0.087656 21.398668 0.180586 21.828907 0.399803 c +22.518520 0.751179 23.079195 1.311853 23.430571 2.001467 c +23.649788 2.431705 23.742718 2.899416 23.786951 3.440796 c +23.830042 3.968204 23.830038 4.620852 23.830032 5.436470 c +23.830032 5.436536 l +23.830032 5.465342 l +23.830032 12.997283 l +23.830048 13.095503 l +23.830235 13.854461 23.830357 14.356583 23.724682 14.787757 c +23.399588 16.114222 22.363911 17.149897 21.037447 17.474993 c +20.606276 17.580666 20.104155 17.580544 19.345201 17.580357 c +19.246973 17.580341 l +18.981796 17.580341 18.903906 17.581457 18.833412 17.589970 c +18.591112 17.619228 18.361469 17.714350 18.169449 17.864992 c +18.113583 17.908821 18.057718 17.963108 17.870209 18.150616 c +17.072515 18.948311 l +17.024576 18.996279 l +16.725307 19.295834 16.499371 19.521982 16.229063 19.687628 c +15.990620 19.833746 15.730661 19.941423 15.458736 20.006708 c +15.150473 20.080715 14.830803 20.080563 14.407373 20.080362 c +14.339548 20.080341 l +9.490515 20.080341 l +h +8.681808 18.713455 m +8.817650 18.746067 8.969681 18.750341 9.490515 18.750341 c +14.339548 18.750341 l +14.860382 18.750341 15.012413 18.746067 15.148252 18.713455 c +15.284472 18.680752 15.414694 18.626812 15.534140 18.553616 c +15.653254 18.480623 15.763777 18.376143 16.132063 18.007858 c +16.929756 17.210163 l +16.954220 17.185692 l +16.954237 17.185675 l +17.106804 17.033035 17.221756 16.918030 17.348524 16.818577 c +17.731848 16.517857 18.190273 16.327971 18.673967 16.269562 c +18.833941 16.250244 18.996555 16.250284 19.212389 16.250336 c +19.246973 16.250341 l +20.139725 16.250341 20.466656 16.245523 20.720854 16.183224 c +21.565954 15.976102 22.225792 15.316265 22.432913 14.471165 c +22.495213 14.216966 22.500031 13.890034 22.500031 12.997283 c +22.500031 5.465342 l +22.500031 4.614289 22.499514 4.015995 22.461367 3.549101 c +22.423855 3.089968 22.353294 2.816771 22.245531 2.605274 c +22.021667 2.165916 21.664457 1.808706 21.225100 1.584843 c +21.013603 1.477079 20.740406 1.406519 20.281273 1.369007 c +19.814379 1.330860 19.216084 1.330343 18.365032 1.330343 c +5.465031 1.330343 l +4.613979 1.330343 4.015684 1.330860 3.548789 1.369007 c +3.089657 1.406519 2.816460 1.477079 2.604963 1.584843 c +2.165605 1.808706 1.808395 2.165916 1.584531 2.605274 c +1.476768 2.816771 1.406208 3.089968 1.368695 3.549101 c +1.330548 4.015995 1.330031 4.614290 1.330031 5.465342 c +1.330031 12.997284 l +1.330031 13.890034 1.334849 14.216966 1.397150 14.471165 c +1.604271 15.316265 2.264108 15.976102 3.109208 16.183224 c +3.363407 16.245523 3.690338 16.250341 4.583089 16.250341 c +4.617674 16.250336 l +4.617689 16.250336 l +4.833515 16.250284 4.996125 16.250244 5.156096 16.269562 c +5.639788 16.327971 6.098214 16.517857 6.481537 16.818577 c +6.608315 16.918037 6.723272 17.033049 6.875851 17.185703 c +6.900304 17.210163 l +7.697999 18.007858 l +8.066284 18.376143 8.176808 18.480623 8.295923 18.553616 c +8.415368 18.626812 8.545589 18.680752 8.681808 18.713455 c +h +8.819138 12.449797 m +9.606685 13.253181 10.702194 13.750379 11.915030 13.750379 c +13.964883 13.750379 15.683014 12.327185 16.134258 10.415339 c +14.848875 10.415339 l +14.636918 10.415339 14.521129 10.168130 14.656816 10.005297 c +16.622980 7.645809 l +16.722927 7.525869 16.907139 7.525866 17.007090 7.645802 c +18.973408 10.005290 l +19.109104 10.168120 18.993317 10.415339 18.781355 10.415339 c +17.491955 10.415339 l +17.019377 13.067384 14.702655 15.080379 11.915030 15.080379 c +10.330412 15.080379 8.896755 14.428887 7.869370 13.380838 c +7.612269 13.118567 7.616461 12.697534 7.878733 12.440434 c +8.141004 12.183333 8.562037 12.187525 8.819138 12.449797 c +h +6.338119 8.415344 m +5.048842 8.415344 l +4.836884 8.415344 4.721095 8.662557 4.856786 8.825389 c +6.822981 11.184870 l +6.922931 11.304811 7.107148 11.304810 7.207097 11.184867 c +9.173254 8.825387 l +9.308942 8.662554 9.193151 8.415344 8.981194 8.415344 c +7.695821 8.415344 l +8.147092 6.503536 9.865205 5.080379 11.915030 5.080379 c +13.096758 5.080379 14.166923 5.552347 14.949532 6.319569 c +15.211796 6.576675 15.632830 6.572495 15.889937 6.310231 c +16.147045 6.047967 16.142864 5.626933 15.880600 5.369825 c +14.859452 4.368757 13.458792 3.750378 11.915030 3.750378 c +9.127432 3.750378 6.810725 5.763336 6.338119 8.415344 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 5480 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000005570 00000 n +0000005593 00000 n +0000005766 00000 n +0000005840 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +5899 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 384b326944..24cacd040a 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -9499,13 +9499,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } strongSelf.beginMediaRecordingRequestId += 1 strongSelf.lockMediaRecordingRequestId = nil - strongSelf.stopMediaRecorder() + strongSelf.stopMediaRecorder(pause: true) }, lockMediaRecording: { [weak self] in guard let strongSelf = self else { return } strongSelf.lockMediaRecordingRequestId = strongSelf.beginMediaRecordingRequestId strongSelf.lockMediaRecorder() + }, resumeMediaRecording: { [weak self] in + guard let self else { + return + } + self.resumeMediaRecorder() }, deleteRecordedMedia: { [weak self] in self?.deleteMediaRecording() }, sendRecordedMedia: { [weak self] silentPosting, viewOnce in @@ -15546,98 +15551,106 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if let audioRecorderValue = self.audioRecorderValue { - audioRecorderValue.stop() + switch action { + case .pause: + audioRecorderValue.pause() + default: + audioRecorderValue.stop() + } switch updatedAction { - case .dismiss: - self.chatDisplayNode.updateRecordedMediaDeleted(true) + case .dismiss: + self.chatDisplayNode.updateRecordedMediaDeleted(true) + self.audioRecorder.set(.single(nil)) + case .preview, .pause: + if case .preview = updatedAction { self.audioRecorder.set(.single(nil)) - case .preview: - self.audioRecorder.set(.single(nil)) - self.updateChatPresentationInterfaceState(animated: true, interactive: true, { - $0.updatedInputTextPanelState { panelState in - return panelState.withUpdatedMediaRecordingState(.waitingForPreview) + } + self.updateChatPresentationInterfaceState(animated: true, interactive: true, { + $0.updatedInputTextPanelState { panelState in + return panelState.withUpdatedMediaRecordingState(.waitingForPreview) + } + }) + let _ = (audioRecorderValue.takenRecordedData() + |> deliverOnMainQueue).startStandalone(next: { [weak self] data in + if let strongSelf = self, let data = data { + if data.duration < 0.5 { + strongSelf.recorderFeedback?.error() + strongSelf.recorderFeedback = nil + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { + $0.updatedInputTextPanelState { panelState in + return panelState.withUpdatedMediaRecordingState(nil) + } + }) + } else if let waveform = data.waveform { + let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max), size: Int64(data.compressedData.count)) + + strongSelf.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data.compressedData) + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { + $0.updatedRecordedMediaPreview(ChatRecordedMediaPreview(resource: resource, duration: Int32(data.duration), fileSize: Int32(data.compressedData.count), waveform: AudioWaveform(bitstream: waveform, bitsPerSample: 5))).updatedInputTextPanelState { panelState in + return panelState.withUpdatedMediaRecordingState(nil) + } + }) + strongSelf.recorderFeedback = nil + strongSelf.updateDownButtonVisibility() } - }) - let _ = (audioRecorderValue.takenRecordedData() |> deliverOnMainQueue).startStandalone(next: { [weak self] data in - if let strongSelf = self, let data = data { - if data.duration < 0.5 { - strongSelf.recorderFeedback?.error() - strongSelf.recorderFeedback = nil - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { - $0.updatedInputTextPanelState { panelState in - return panelState.withUpdatedMediaRecordingState(nil) + } + }) + case let .send(viewOnce): + self.chatDisplayNode.updateRecordedMediaDeleted(false) + let _ = (audioRecorderValue.takenRecordedData() + |> deliverOnMainQueue).startStandalone(next: { [weak self] data in + if let strongSelf = self, let data = data { + if data.duration < 0.5 { + strongSelf.recorderFeedback?.error() + strongSelf.recorderFeedback = nil + strongSelf.audioRecorder.set(.single(nil)) + } else { + let randomId = Int64.random(in: Int64.min ... Int64.max) + + let resource = LocalFileMediaResource(fileId: randomId) + strongSelf.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data.compressedData) + + let waveformBuffer: Data? = data.waveform + + let correlationId = Int64.random(in: 0 ..< Int64.max) + var usedCorrelationId = false + + if strongSelf.chatDisplayNode.shouldAnimateMessageTransition, let textInputPanelNode = strongSelf.chatDisplayNode.textInputPanelNode, let micButton = textInputPanelNode.micButton { + usedCorrelationId = true + strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .audioMicInput(ChatMessageTransitionNodeImpl.Source.AudioMicInput(micButton: micButton)), initiated: { + guard let strongSelf = self else { + return } - }) - } else if let waveform = data.waveform { - let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max), size: Int64(data.compressedData.count)) - - strongSelf.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data.compressedData) - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { - $0.updatedRecordedMediaPreview(ChatRecordedMediaPreview(resource: resource, duration: Int32(data.duration), fileSize: Int32(data.compressedData.count), waveform: AudioWaveform(bitstream: waveform, bitsPerSample: 5))).updatedInputTextPanelState { panelState in - return panelState.withUpdatedMediaRecordingState(nil) - } - }) - strongSelf.recorderFeedback = nil - strongSelf.updateDownButtonVisibility() - } - } - }) - case let .send(viewOnce): - self.chatDisplayNode.updateRecordedMediaDeleted(false) - let _ = (audioRecorderValue.takenRecordedData() - |> deliverOnMainQueue).startStandalone(next: { [weak self] data in - if let strongSelf = self, let data = data { - if data.duration < 0.5 { - strongSelf.recorderFeedback?.error() - strongSelf.recorderFeedback = nil - strongSelf.audioRecorder.set(.single(nil)) - } else { - let randomId = Int64.random(in: Int64.min ... Int64.max) - - let resource = LocalFileMediaResource(fileId: randomId) - strongSelf.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data.compressedData) - - let waveformBuffer: Data? = data.waveform - - let correlationId = Int64.random(in: 0 ..< Int64.max) - var usedCorrelationId = false - - if strongSelf.chatDisplayNode.shouldAnimateMessageTransition, let textInputPanelNode = strongSelf.chatDisplayNode.textInputPanelNode, let micButton = textInputPanelNode.micButton { - usedCorrelationId = true - strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .audioMicInput(ChatMessageTransitionNodeImpl.Source.AudioMicInput(micButton: micButton)), initiated: { - guard let strongSelf = self else { - return - } - strongSelf.audioRecorder.set(.single(nil)) - }) - } else { strongSelf.audioRecorder.set(.single(nil)) - } - - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.chatDisplayNode.collapseInput() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } - }) - } - }, usedCorrelationId ? correlationId : nil) - - var attributes: [MessageAttribute] = [] - if viewOnce { - attributes.append(AutoremoveTimeoutMessageAttribute(timeout: viewOnceTimeout, countdownBeginTime: nil)) - } - - strongSelf.sendMessages([.message(text: "", attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(data.compressedData.count), attributes: [.Audio(isVoice: true, duration: Int(data.duration), title: nil, performer: nil, waveform: waveformBuffer)])), threadId: strongSelf.chatLocation.threadId, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: correlationId, bubbleUpEmojiOrStickersets: [])]) - - strongSelf.recorderFeedback?.tap() - strongSelf.recorderFeedback = nil + }) + } else { + strongSelf.audioRecorder.set(.single(nil)) } + + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } + }) + } + }, usedCorrelationId ? correlationId : nil) + + var attributes: [MessageAttribute] = [] + if viewOnce { + attributes.append(AutoremoveTimeoutMessageAttribute(timeout: viewOnceTimeout, countdownBeginTime: nil)) + } + + strongSelf.sendMessages([.message(text: "", attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(data.compressedData.count), attributes: [.Audio(isVoice: true, duration: Int(data.duration), title: nil, performer: nil, waveform: waveformBuffer)])), threadId: strongSelf.chatLocation.threadId, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: correlationId, bubbleUpEmojiOrStickersets: [])]) + + strongSelf.recorderFeedback?.tap() + strongSelf.recorderFeedback = nil } - }) + } + }) } } else if let videoRecorderValue = self.videoRecorderValue { if case .send = updatedAction { @@ -15660,10 +15673,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - func stopMediaRecorder() { + func stopMediaRecorder(pause: Bool = false) { if let audioRecorderValue = self.audioRecorderValue { if let _ = self.presentationInterfaceState.inputTextPanelState.mediaRecordingState { - self.dismissMediaRecorder(.preview) + self.dismissMediaRecorder(pause ? .pause : .preview) } else { audioRecorderValue.stop() self.audioRecorder.set(.single(nil)) @@ -15681,6 +15694,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + func resumeMediaRecorder() { + if let audioRecorderValue = self.audioRecorderValue { + audioRecorderValue.resume() + + self.updateChatPresentationInterfaceState(animated: true, interactive: true, { + $0.updatedInputTextPanelState { panelState in + return panelState.withUpdatedMediaRecordingState(.audio(recorder: audioRecorderValue, isLocked: true)) + }.updatedRecordedMediaPreview(nil) + }) + } + } + func lockMediaRecorder() { if self.presentationInterfaceState.inputTextPanelState.mediaRecordingState != nil { self.updateChatPresentationInterfaceState(animated: true, interactive: true, { diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index d9eadc9668..6edcc48582 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -21,6 +21,7 @@ func chatHistoryEntriesForView( selectedMessages: Set?, presentationData: ChatPresentationData, historyAppearsCleared: Bool, + skipViewOnceMedia: Bool, pendingUnpinnedAllMessages: Bool, pendingRemovedMessages: Set, associatedData: ChatMessageItemAssociatedData, @@ -152,6 +153,10 @@ func chatHistoryEntriesForView( } } + if skipViewOnceMedia, message.minAutoremoveOrClearTimeout != nil { + continue loop + } + var contentTypeHint: ChatMessageEntryContentType = .generic for media in message.media { diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 44f0750b6c..2a054b7901 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -1588,6 +1588,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto selectedMessages: selectedMessages, presentationData: chatPresentationData, historyAppearsCleared: historyAppearsCleared, + skipViewOnceMedia: mode != .bubbles, pendingUnpinnedAllMessages: pendingUnpinnedAllMessages, pendingRemovedMessages: pendingRemovedMessages, associatedData: associatedData, diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 9a2235765a..ba5d08c8c5 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -909,7 +909,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState actions.insert(.separator, at: 1) } - if !hasRateTranscription { + if !hasRateTranscription && message.minAutoremoveOrClearTimeout == nil { for media in message.media { if let file = media as? TelegramMediaFile, let size = file.size, size < 1 * 1024 * 1024, let duration = file.duration, duration < 60, (["audio/mpeg", "audio/mp3", "audio/mpeg3", "audio/ogg"] as [String]).contains(file.mimeType.lowercased()) { let fileName = file.fileName ?? "Tone" diff --git a/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift index f269156706..44a8142f0d 100644 --- a/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift @@ -69,6 +69,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode { private var viewOnce = false let viewOnceButton: ChatRecordingViewOnceButtonNode + let recordMoreButton: ChatRecordingViewOnceButtonNode private let waveformNode: AudioWaveformNode private let waveformForegroundNode: AudioWaveformNode @@ -104,7 +105,8 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode { self.sendButton.displaysAsynchronously = false self.sendButton.setImage(PresentationResourcesChat.chatInputPanelSendButtonImage(theme), for: []) - self.viewOnceButton = ChatRecordingViewOnceButtonNode() + self.viewOnceButton = ChatRecordingViewOnceButtonNode(icon: .viewOnce) + self.recordMoreButton = ChatRecordingViewOnceButtonNode(icon: .recordMore) self.waveformBackgroundNode = ASImageNode() self.waveformBackgroundNode.isLayerBacked = true @@ -173,6 +175,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode { self.deleteButton.addTarget(self, action: #selector(self.deletePressed), forControlEvents: [.touchUpInside]) self.sendButton.addTarget(self, action: #selector(self.sendPressed), forControlEvents: [.touchUpInside]) self.viewOnceButton.addTarget(self, action: #selector(self.viewOncePressed), forControlEvents: [.touchUpInside]) + self.recordMoreButton.addTarget(self, action: #selector(self.recordMorePressed), forControlEvents: [.touchUpInside]) self.waveformButton.addTarget(self, action: #selector(self.waveformPressed), forControlEvents: .touchUpInside) } @@ -197,6 +200,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode { if let viewForOverlayContent = self.viewForOverlayContent { viewForOverlayContent.addSubnode(self.viewOnceButton) + viewForOverlayContent.addSubnode(self.recordMoreButton) } } @@ -271,7 +275,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode { } } - if isFirstTime { + if isFirstTime, !self.viewOnceButton.isHidden { self.maybePresentViewOnceTooltip() } @@ -282,9 +286,13 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode { self.binNode.frame = self.deleteButton.bounds let viewOnceSize = self.viewOnceButton.update(theme: interfaceState.theme) - let viewOnceButtonFrame = CGRect(origin: CGPoint(x: width - rightInset - 44.0 - UIScreenPixel, y: -64.0), size: viewOnceSize) + let viewOnceButtonFrame = CGRect(origin: CGPoint(x: width - rightInset - 44.0 - UIScreenPixel, y: -64.0 - 53.0), size: viewOnceSize) transition.updateFrame(node: self.viewOnceButton, frame: viewOnceButtonFrame) + let recordMoreSize = self.recordMoreButton.update(theme: interfaceState.theme) + let recordMoreButtonFrame = CGRect(origin: CGPoint(x: width - rightInset - 44.0 - UIScreenPixel, y: -64.0), size: recordMoreSize) + transition.updateFrame(node: self.recordMoreButton, frame: recordMoreButtonFrame) + var isScheduledMessages = false if case .scheduledMessages = interfaceState.subject { isScheduledMessages = true @@ -332,6 +340,7 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode { prevTextInputPanelNode.viewOnceButton.isHidden = true prevTextInputPanelNode.viewOnce = false + self.viewOnceButton.layer.animatePosition(from: prevTextInputPanelNode.viewOnceButton.position, to: self.viewOnceButton.position, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in prevTextInputPanelNode.viewOnceButton.isHidden = false prevTextInputPanelNode.viewOnceButton.update(isSelected: false, animated: false) @@ -420,6 +429,12 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode { } } + @objc private func recordMorePressed() { + self.tooltipController?.dismiss() + + self.interfaceInteraction?.resumeMediaRecording() + } + private func displayViewOnceTooltip(text: String, hasIcon: Bool) { guard let context = self.context, let parentController = self.interfaceInteraction?.chatController() else { return @@ -515,19 +530,28 @@ private final class PlayPauseIconNode: ManagedAnimationNode { final class ChatRecordingViewOnceButtonNode: HighlightTrackingButtonNode { + enum Icon { + case viewOnce + case recordMore + } + + private let icon: Icon + private let backgroundNode: ASImageNode private let iconNode: ASImageNode private var theme: PresentationTheme? - override init(pointerStyle: PointerStyle? = nil) { + init(icon: Icon) { + self.icon = icon + self.backgroundNode = ASImageNode() self.backgroundNode.isUserInteractionEnabled = false self.iconNode = ASImageNode() self.iconNode.isUserInteractionEnabled = false - super.init(pointerStyle: pointerStyle) + super.init(pointerStyle: .default) self.addSubnode(self.backgroundNode) self.addSubnode(self.iconNode) @@ -576,7 +600,9 @@ final class ChatRecordingViewOnceButtonNode: HighlightTrackingButtonNode { } if updated { - self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: self.innerIsSelected ? "Media Gallery/ViewOnceEnabled" : "Media Gallery/ViewOnce"), color: theme.chat.inputPanel.panelControlAccentColor) + if case .viewOnce = self.icon { + self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: self.innerIsSelected ? "Media Gallery/ViewOnceEnabled" : "Media Gallery/ViewOnce"), color: theme.chat.inputPanel.panelControlAccentColor) + } } } @@ -588,9 +614,16 @@ final class ChatRecordingViewOnceButtonNode: HighlightTrackingButtonNode { self.theme = theme self.backgroundNode.image = generateFilledCircleImage(diameter: innerSize.width, color: theme.rootController.navigationBar.opaqueBackgroundColor, strokeColor: theme.chat.inputPanel.panelSeparatorColor, strokeWidth: 0.5, backgroundColor: nil) - self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: self.innerIsSelected ? "Media Gallery/ViewOnceEnabled" : "Media Gallery/ViewOnce"), color: theme.chat.inputPanel.panelControlAccentColor) - } + + switch self.icon { + case .viewOnce: + self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: self.innerIsSelected ? "Media Gallery/ViewOnceEnabled" : "Media Gallery/ViewOnce"), color: theme.chat.inputPanel.panelControlAccentColor) + case .recordMore: + self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconMicrophone"), color: theme.chat.inputPanel.panelControlAccentColor) + } + } + if let backgroundImage = self.backgroundNode.image { let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width / 2.0 - backgroundImage.size.width / 2.0), y: floorToScreenPixels(size.height / 2.0 - backgroundImage.size.height / 2.0)), size: backgroundImage.size) self.backgroundNode.frame = backgroundFrame diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 42b2922bba..7d23052d53 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -850,7 +850,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self.counterTextNode = ImmediateTextNode() self.counterTextNode.textAlignment = .center - self.viewOnceButton = ChatRecordingViewOnceButtonNode() + self.viewOnceButton = ChatRecordingViewOnceButtonNode(icon: .viewOnce) super.init() @@ -2634,7 +2634,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } transition.updateAlpha(node: self.viewOnceButton, alpha: viewOnceIsVisible ? 1.0 : 0.0) transition.updateTransformScale(node: self.viewOnceButton, scale: viewOnceIsVisible ? 1.0 : 0.01) - if let _ = interfaceState.renderedPeer?.peer as? TelegramUser { + if let user = interfaceState.renderedPeer?.peer as? TelegramUser, user.id != interfaceState.accountPeerId && user.botInfo == nil { self.viewOnceButton.isHidden = false } else { self.viewOnceButton.isHidden = true diff --git a/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift b/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift index 3aa85a5df0..24053992e1 100644 --- a/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift +++ b/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift @@ -447,6 +447,18 @@ final class ManagedAudioRecorderContext { } } + func pause() { + assert(self.queue.isCurrent()) + + self.paused = true + } + + func resume() { + assert(self.queue.isCurrent()) + + self.paused = false + } + func stop() { assert(self.queue.isCurrent()) @@ -488,7 +500,7 @@ final class ManagedAudioRecorderContext { free(buffer.mData) } - if !self.processSamples { + if !self.processSamples && !self.paused { return } @@ -506,7 +518,7 @@ final class ManagedAudioRecorderContext { var currentEncoderPacketSize = 0 while currentEncoderPacketSize < encoderPacketSizeInBytes { - if audioBuffer.count != 0 { + if self.audioBuffer.count != 0 { let takenBytes = min(self.audioBuffer.count, encoderPacketSizeInBytes - currentEncoderPacketSize) if takenBytes != 0 { self.audioBuffer.withUnsafeBytes { rawBytes -> Void in @@ -699,6 +711,22 @@ final class ManagedAudioRecorderImpl: ManagedAudioRecorder { } } + func pause() { + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + context.pause() + } + } + } + + func resume() { + self.queue.async { + if let context = self.contextRef?.takeUnretainedValue() { + context.resume() + } + } + } + func stop() { self.queue.async { if let context = self.contextRef?.takeUnretainedValue() {