Various fixes

This commit is contained in:
Ilya Laktyushin 2024-01-04 17:15:12 +04:00
parent 340cc2c385
commit f02722c954
27 changed files with 732 additions and 154 deletions

View File

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

View File

@ -228,6 +228,8 @@ public protocol ManagedAudioRecorder: AnyObject {
var recordingState: Signal<AudioRecordingState, NoError> { get }
func start()
func pause()
func resume()
func stop()
func takenRecordedData() -> Signal<RecordedAudioData?, NoError>
}

View File

@ -826,6 +826,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
}, finishMediaRecording: { _ in
}, stopMediaRecording: {
}, lockMediaRecording: {
}, resumeMediaRecording: {
}, deleteRecordedMedia: {
}, sendRecordedMedia: { _, _ in
}, displayRestrictedInfo: { _, _ in

View File

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

View File

@ -8,7 +8,7 @@ import VideoToolbox
private let queue = Queue()
func cutoutStickerImage(from image: UIImage) -> Signal<UIImage?, NoError> {
public func cutoutStickerImage(from image: UIImage) -> Signal<UIImage?, NoError> {
if #available(iOS 17.0, *) {
guard let cgImage = image.cgImage else {
return .single(nil)

View File

@ -440,7 +440,6 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
}
|> take(1)
let product: Signal<InAppPurchaseManager.Product?, NoError> = 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 {

View File

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

View File

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

View File

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

View File

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

View File

@ -102,6 +102,7 @@ public final class ChatRecentActionsController: TelegramBaseController {
}, finishMediaRecording: { _ in
}, stopMediaRecording: {
}, lockMediaRecording: {
}, resumeMediaRecording: {
}, deleteRecordedMedia: {
}, sendRecordedMedia: { _, _ in
}, displayRestrictedInfo: { _, _ in

View File

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

View File

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

View File

@ -591,6 +591,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
}, finishMediaRecording: { _ in
}, stopMediaRecording: {
}, lockMediaRecording: {
}, resumeMediaRecording: {
}, deleteRecordedMedia: {
}, sendRecordedMedia: { _, _ in
}, displayRestrictedInfo: { _, _ in

View File

@ -1,9 +1,9 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}
}

View File

@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "pausevoicecideo_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

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

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "switchcamera_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

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

View File

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

View File

@ -21,6 +21,7 @@ func chatHistoryEntriesForView(
selectedMessages: Set<MessageId>?,
presentationData: ChatPresentationData,
historyAppearsCleared: Bool,
skipViewOnceMedia: Bool,
pendingUnpinnedAllMessages: Bool,
pendingRemovedMessages: Set<MessageId>,
associatedData: ChatMessageItemAssociatedData,
@ -152,6 +153,10 @@ func chatHistoryEntriesForView(
}
}
if skipViewOnceMedia, message.minAutoremoveOrClearTimeout != nil {
continue loop
}
var contentTypeHint: ChatMessageEntryContentType = .generic
for media in message.media {

View File

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

View File

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

View File

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

View File

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

View File

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