From f02722c9549393088070019266c1d23404dc49be Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 4 Jan 2024 17:15:12 +0400 Subject: [PATCH 1/4] 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() { From 90876a9ac4314aababd8d272ee1dfb014968c39b Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 4 Jan 2024 19:07:04 +0400 Subject: [PATCH 2/4] Various improvements --- .../TGModernConversationInputMicButton.h | 2 ++ .../TGModernConversationInputMicButton.m | 28 +++++++++++++++- .../ChatTextInputMediaRecordingButton.swift | 10 +++--- .../ChatRecordingPreviewInputPanelNode.swift | 5 +++ .../Sources/ChatTextInputPanelNode.swift | 33 +++++++++++++++---- .../Sources/ManagedAudioRecorder.swift | 2 +- 6 files changed, 67 insertions(+), 13 deletions(-) diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationInputMicButton.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationInputMicButton.h index ed78e3cbef..94e5eff7d9 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationInputMicButton.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernConversationInputMicButton.h @@ -87,6 +87,8 @@ - (void)_commitLocked; +- (void)lockImmediately; + - (void)setHidesPanelOnLock; - (UIView *)createLockPanelView; diff --git a/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m b/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m index e388791013..9e570cdd40 100644 --- a/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m +++ b/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m @@ -473,7 +473,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius _outerCircleView.alpha = 0.2f; _decoration.alpha = 0.2; - _lockPanelWrapperView.transform = CGAffineTransformMakeTranslation(0.0f, 100.0f); + _lockPanelWrapperView.transform = CGAffineTransformMakeTranslation(0.0f, _locked ? 36.0 : 100.0f); _lockPanelWrapperView.alpha = 0.0f; _lock.transform = CGAffineTransformIdentity; @@ -598,6 +598,32 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius return iconImage; } +- (void)lockImmediately { + _lockView.lockness = 1.0; + [_lock updateLockness:1.0]; + + UIImage *icon = TGComponentsImageNamed(@"RecordSendIcon"); + [self setIcon:TGTintedImage(icon, _pallete != nil && !_hidesPanelOnLock ? _pallete.iconColor : [UIColor whiteColor])]; + + _currentScale = 1; + _cancelTargetTranslation = 0; + + id delegate = _delegate; + if ([delegate respondsToSelector:@selector(micButtonInteractionUpdateCancelTranslation:)]) + [delegate micButtonInteractionUpdateCancelTranslation:-_cancelTargetTranslation]; + + _lockPanelView.frame = CGRectMake(_lockPanelView.frame.origin.x, 40.0f, _lockPanelView.frame.size.width, 72.0f - 32.0f); + _lockView.transform = CGAffineTransformMakeTranslation(0.0f, -11.0f); + _lock.transform = CGAffineTransformMakeTranslation(0.0f, -16.0f); + _lockArrowView.transform = CGAffineTransformMakeTranslation(0.0f, -39.0f); + _lockArrowView.alpha = 0.0f; + + _stopButton.userInteractionEnabled = true; + [UIView animateWithDuration:0.25 delay:0.56 options:kNilOptions animations:^ + { + _stopButton.alpha = 1.0f; + } completion:nil]; +} - (void)animateLock { if (!_animatedIn) { diff --git a/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/Sources/ChatTextInputMediaRecordingButton.swift b/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/Sources/ChatTextInputMediaRecordingButton.swift index 822eacf1e8..83337ed8d5 100644 --- a/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/Sources/ChatTextInputMediaRecordingButton.swift +++ b/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/Sources/ChatTextInputMediaRecordingButton.swift @@ -252,7 +252,6 @@ public final class ChatTextInputMediaRecordingButton: TGModernConversationInputM if let audioRecorder = self.audioRecorder { self.micLevelDisposable?.set(audioRecorder.micLevel.start(next: { [weak self] level in Queue.mainQueue().async { - //self?.recordingOverlay?.addImmediateMicLevel(CGFloat(level)) self?.addMicLevel(CGFloat(level)) } })) @@ -275,7 +274,6 @@ public final class ChatTextInputMediaRecordingButton: TGModernConversationInputM if let videoRecordingStatus = self.videoRecordingStatus { self.micLevelDisposable?.set(videoRecordingStatus.micLevel.start(next: { [weak self] level in Queue.mainQueue().async { - //self?.recordingOverlay?.addImmediateMicLevel(CGFloat(level)) self?.addMicLevel(CGFloat(level)) } })) @@ -411,7 +409,7 @@ public final class ChatTextInputMediaRecordingButton: TGModernConversationInputM animationName = "anim_micToVideo" } - let _ = animationView.update( + let _ = self.animationView.update( transition: .immediate, component: AnyComponent(LottieComponent( content: LottieComponent.AppBundleContent(name: animationName), @@ -421,7 +419,7 @@ public final class ChatTextInputMediaRecordingButton: TGModernConversationInputM containerSize: animationFrame.size ) - if let view = animationView.view as? LottieComponent.View { + if let view = self.animationView.view as? LottieComponent.View { view.isUserInteractionEnabled = false if view.superview == nil { self.insertSubview(view, at: 0) @@ -537,6 +535,10 @@ public final class ChatTextInputMediaRecordingButton: TGModernConversationInputM micButtonInteractionStopped() } + public func lock() { + super._commitLocked() + } + override public func animateIn() { super.animateIn() diff --git a/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift index 44a8142f0d..c06841f2a1 100644 --- a/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecordingPreviewInputPanelNode.swift @@ -346,6 +346,9 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode { prevTextInputPanelNode.viewOnceButton.update(isSelected: false, animated: false) }) + self.recordMoreButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.recordMoreButton.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + if let audioRecordingDotNode = prevTextInputPanelNode.audioRecordingDotNode { let startAlpha = CGFloat(audioRecordingDotNode.layer.presentation()?.opacity ?? 1.0) audioRecordingDotNode.layer.removeAllAnimations() @@ -388,6 +391,8 @@ final class ChatRecordingPreviewInputPanelNode: ChatInputPanelNode { ) { [weak self, weak prevTextInputPanelNode] finished in if prevTextInputPanelNode?.supernode === self { prevTextInputPanelNode?.removeFromSupernode() + prevTextInputPanelNode?.finishedTransitionToPreview = true + prevTextInputPanelNode?.requestLayout() } } } diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 7d23052d53..40c20b121e 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -769,6 +769,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch private var spoilersRevealed = false + private var animatingTransition = false + var finishedTransitionToPreview: Bool? + private var touchDownGestureRecognizer: TouchDownGestureRecognizer? var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)? @@ -1292,7 +1295,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch return minimalHeight } - private var animatingTransition = false private func animateBotButtonInFromMenu(transition: ContainedViewLayoutTransition) { guard !self.animatingTransition else { return @@ -1412,6 +1414,13 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } } + func requestLayout() { + guard let presentationInterfaceState = self.presentationInterfaceState, let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout else { + return + } + let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + } + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { let previousAdditionalSideInsets = self.validLayout?.4 self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) @@ -1986,7 +1995,11 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch var hideMicButton = false var audioRecordingItemsAlpha: CGFloat = 1 - if mediaRecordingState != nil || interfaceState.recordedMediaPreview != nil { + if mediaRecordingState != nil || (interfaceState.recordedMediaPreview != nil && self.finishedTransitionToPreview != true) { + if interfaceState.recordedMediaPreview != nil { + self.finishedTransitionToPreview = false + } + audioRecordingItemsAlpha = 0 let audioRecordingInfoContainerNode: ASDisplayNode @@ -2033,7 +2046,11 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch if let mediaRecordingState = mediaRecordingState { switch mediaRecordingState { - case let .audio(recorder, _): + case let .audio(recorder, isLocked): + let hadAudioRecorder = self.actionButtons.micButton.audioRecorder != nil + if !hadAudioRecorder, isLocked { + self.actionButtons.micButton.lock() + } self.actionButtons.micButton.audioRecorder = recorder audioRecordingTimeNode.audioRecorder = recorder case let .video(status, _): @@ -2176,6 +2193,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch audioRecordingCancelIndicator.layer.animateAlpha(from: CGFloat(audioRecordingCancelIndicator.layer.presentation()?.opacity ?? 1), to: 0, duration: 0.15, delay: 0, removeOnCompletion: false) } } else { + self.finishedTransitionToPreview = nil + var update = self.actionButtons.micButton.audioRecorder != nil || self.actionButtons.micButton.videoRecordingStatus != nil self.actionButtons.micButton.audioRecorder = nil self.actionButtons.micButton.videoRecordingStatus = nil @@ -2200,13 +2219,13 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self?.audioRecordingDotNode = nil - audioRecordingDotNode.layer.animateScale(from: 1, to: 0.3, duration: 0.15, delay: 0, removeOnCompletion: false) - audioRecordingDotNode.layer.animateAlpha(from: CGFloat(audioRecordingDotNode.layer.presentation()?.opacity ?? 1), to: 0.0, duration: 0.15, delay: 0, removeOnCompletion: false) { [weak audioRecordingDotNode] _ in + audioRecordingDotNode.layer.animateScale(from: 1.0, to: 0.3, duration: 0.15, delay: 0.0, removeOnCompletion: false) + audioRecordingDotNode.layer.animateAlpha(from: CGFloat(audioRecordingDotNode.layer.presentation()?.opacity ?? 1), to: 0.0, duration: 0.15, delay: 0.0, removeOnCompletion: false) { [weak audioRecordingDotNode] _ in audioRecordingDotNode?.removeFromSupernode() } - self?.attachmentButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false) - self?.attachmentButton.layer.animateScale(from: 0.3, to: 1.0, duration: 0.15, delay: 0, removeOnCompletion: false) + self?.attachmentButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: 0.0, removeOnCompletion: false) + self?.attachmentButton.layer.animateScale(from: 0.3, to: 1.0, duration: 0.15, delay: 0.0, removeOnCompletion: false) } if update && !self.audioRecordingDotNodeDismissed { diff --git a/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift b/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift index 24053992e1..a78fc96e3f 100644 --- a/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift +++ b/submodules/TelegramUI/Sources/ManagedAudioRecorder.swift @@ -500,7 +500,7 @@ final class ManagedAudioRecorderContext { free(buffer.mData) } - if !self.processSamples && !self.paused { + if !self.processSamples || self.paused { return } From 2bb75e1356c17a2c835fcf5a25a8ada58f6c44c4 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 4 Jan 2024 20:00:45 +0400 Subject: [PATCH 3/4] Add hide read time setting --- .../Sources/AccountContext.swift | 1 + .../Sources/PremiumIntroScreen.swift | 9 + .../PrivacyAndSecurityController.swift | 16 +- .../SelectivePrivacySettingsController.swift | 167 +++++++++++++++--- .../Search/SettingsSearchableItems.swift | 2 +- .../Sources/SharedAccountContext.swift | 2 + 6 files changed, 163 insertions(+), 34 deletions(-) diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index a3f413e354..4bfb363aae 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1016,6 +1016,7 @@ public enum PremiumIntroSource { case nameColor case similarChannels case wallpapers + case presence } public enum PremiumGiftSource: Equatable { diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 992399348e..f384ab27f1 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -253,6 +253,12 @@ public enum PremiumSource: Equatable { } else { return false } + case .presence: + if case .presence = rhs { + return true + } else { + return false + } } } @@ -293,6 +299,7 @@ public enum PremiumSource: Equatable { case nameColor case similarChannels case wallpapers + case presence var identifier: String? { switch self { @@ -372,6 +379,8 @@ public enum PremiumSource: Equatable { return "similar_channels" case .wallpapers: return "wallpapers" + case .presence: + return "presence" } } } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift index 3c686730ab..ad1076b633 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift @@ -767,7 +767,7 @@ public func privacyAndSecurityController( |> deliverOnMainQueue currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info in if let info = info { - pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .presence, current: info.presence, updated: { updated, _, _ in + pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .presence, current: info.presence, globalSettings: info.globalSettings, updated: { updated, _, _, updatedGlobalSettings in if let currentInfoDisposable = currentInfoDisposable { let applySetting: Signal = privacySettingsPromise.get() |> filter { $0 != nil } @@ -790,7 +790,7 @@ public func privacyAndSecurityController( |> deliverOnMainQueue currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info in if let info = info { - pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .groupInvitations, current: info.groupInvitations, updated: { updated, _, _ in + pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .groupInvitations, current: info.groupInvitations, updated: { updated, _, _, _ in if let currentInfoDisposable = currentInfoDisposable { let applySetting: Signal = privacySettingsPromise.get() |> filter { $0 != nil } @@ -823,7 +823,7 @@ public func privacyAndSecurityController( currentInfoDisposable.set((combineLatest(privacySignal, callsSignal) |> deliverOnMainQueue).start(next: { [weak currentInfoDisposable] info, callSettings in if let info = info { - pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .voiceCalls, current: info.voiceCalls, callSettings: (info.voiceCallsP2P, callSettings.0), voipConfiguration: callSettings.1, callIntegrationAvailable: CallKitIntegration.isAvailable, updated: { updated, updatedCallSettings, _ in + pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .voiceCalls, current: info.voiceCalls, callSettings: (info.voiceCallsP2P, callSettings.0), voipConfiguration: callSettings.1, callIntegrationAvailable: CallKitIntegration.isAvailable, updated: { updated, updatedCallSettings, _, _ in if let currentInfoDisposable = currentInfoDisposable, let (updatedCallsPrivacy, updatedCallSettings) = updatedCallSettings { let _ = updateVoiceCallSettingsSettingsInteractively(accountManager: context.sharedContext.accountManager, { _ in return updatedCallSettings @@ -854,7 +854,7 @@ public func privacyAndSecurityController( requestPublicPhotoSetup?(completion) }, requestPublicPhotoRemove: { completion in requestPublicPhotoRemove?(completion) - }, updated: { updated, _, _ in + }, updated: { updated, _, _, _ in if let currentInfoDisposable = currentInfoDisposable { let applySetting: Signal = privacySettingsPromise.get() |> filter { $0 != nil } @@ -877,7 +877,7 @@ public func privacyAndSecurityController( |> deliverOnMainQueue currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info in if let info = info { - pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .forwards, current: info.forwards, updated: { updated, _, _ in + pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .forwards, current: info.forwards, updated: { updated, _, _, _ in if let currentInfoDisposable = currentInfoDisposable { let applySetting: Signal = privacySettingsPromise.get() |> filter { $0 != nil } @@ -900,7 +900,7 @@ public func privacyAndSecurityController( |> deliverOnMainQueue currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info in if let info = info { - pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .phoneNumber, current: info.phoneNumber, phoneDiscoveryEnabled: info.phoneDiscoveryEnabled, updated: { updated, _, updatedDiscoveryEnabled in + pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .phoneNumber, current: info.phoneNumber, phoneDiscoveryEnabled: info.phoneDiscoveryEnabled, updated: { updated, _, updatedDiscoveryEnabled, _ in if let currentInfoDisposable = currentInfoDisposable { let applySetting: Signal = privacySettingsPromise.get() |> filter { $0 != nil } @@ -930,7 +930,7 @@ public func privacyAndSecurityController( if isPremium { if let info = info { - pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .voiceMessages, current: info.voiceMessages, updated: { updated, _, _ in + pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .voiceMessages, current: info.voiceMessages, updated: { updated, _, _, _ in if let currentInfoDisposable = currentInfoDisposable { let applySetting: Signal = privacySettingsPromise.get() |> filter { $0 != nil } @@ -970,7 +970,7 @@ public func privacyAndSecurityController( |> deliverOnMainQueue currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info in if let info = info { - pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .bio, current: info.bio, updated: { updated, _, updatedDiscoveryEnabled in + pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .bio, current: info.bio, updated: { updated, _, updatedDiscoveryEnabled, _ in if let currentInfoDisposable = currentInfoDisposable { let applySetting: Signal = privacySettingsPromise.get() |> filter { $0 != nil } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index be11403600..1f0394522a 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -55,22 +55,35 @@ private final class SelectivePrivacySettingsControllerArguments { let updateCallIntegrationEnabled: ((Bool) -> Void)? let updatePhoneDiscovery: ((Bool) -> Void)? let copyPhoneLink: ((String) -> Void)? - let setPublicPhoto: (() -> Void)? let removePublicPhoto: (() -> Void)? + let updateHideReadTime: ((Bool) -> Void)? + let openPremiumIntro: () -> Void - init(context: AccountContext, updateType: @escaping (SelectivePrivacySettingType) -> Void, openSelective: @escaping (SelectivePrivacySettingsPeerTarget, Bool) -> Void, updateCallP2PMode: ((SelectivePrivacySettingType) -> Void)?, updateCallIntegrationEnabled: ((Bool) -> Void)?, updatePhoneDiscovery: ((Bool) -> Void)?, copyPhoneLink: ((String) -> Void)?, setPublicPhoto: (() -> Void)?, removePublicPhoto: (() -> Void)?) { + init( + context: AccountContext, + updateType: @escaping (SelectivePrivacySettingType) -> Void, + openSelective: @escaping (SelectivePrivacySettingsPeerTarget, Bool) -> Void, + updateCallP2PMode: ((SelectivePrivacySettingType) -> Void)?, + updateCallIntegrationEnabled: ((Bool) -> Void)?, + updatePhoneDiscovery: ((Bool) -> Void)?, + copyPhoneLink: ((String) -> Void)?, + setPublicPhoto: (() -> Void)?, + removePublicPhoto: (() -> Void)?, + updateHideReadTime: ((Bool) -> Void)?, + openPremiumIntro: @escaping () -> Void + ) { self.context = context self.updateType = updateType self.openSelective = openSelective - self.updateCallP2PMode = updateCallP2PMode self.updateCallIntegrationEnabled = updateCallIntegrationEnabled self.updatePhoneDiscovery = updatePhoneDiscovery self.copyPhoneLink = copyPhoneLink - self.setPublicPhoto = setPublicPhoto self.removePublicPhoto = removePublicPhoto + self.updateHideReadTime = updateHideReadTime + self.openPremiumIntro = openPremiumIntro } } @@ -83,6 +96,8 @@ private enum SelectivePrivacySettingsSection: Int32 { case callsIntegrationEnabled case phoneDiscovery case photo + case hideReadTime + case premium } private func stringForUserCount(_ peers: [EnginePeer.Id: SelectivePrivacyPeer], strings: PresentationStrings) -> String { @@ -123,6 +138,10 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { case phoneDiscoveryEverybody(PresentationTheme, String, Bool) case phoneDiscoveryMyContacts(PresentationTheme, String, Bool) case phoneDiscoveryInfo(PresentationTheme, String, String) + case hideReadTime(PresentationTheme, String, Bool) + case hideReadTimeInfo(PresentationTheme, String) + case subscribeToPremium(PresentationTheme, String) + case subscribeToPremiumInfo(PresentationTheme, String) case setPublicPhoto(PresentationTheme, String) case removePublicPhoto(PresentationTheme, String, EnginePeer, TelegramMediaImage?, UIImage?) case publicPhotoInfo(PresentationTheme, String) @@ -145,6 +164,10 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { return SelectivePrivacySettingsSection.phoneDiscovery.rawValue case .setPublicPhoto, .removePublicPhoto, .publicPhotoInfo: return SelectivePrivacySettingsSection.photo.rawValue + case .hideReadTime, .hideReadTimeInfo: + return SelectivePrivacySettingsSection.hideReadTime.rawValue + case .subscribeToPremium, .subscribeToPremiumInfo: + return SelectivePrivacySettingsSection.premium.rawValue } } @@ -206,6 +229,14 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { return 25 case .publicPhotoInfo: return 26 + case .hideReadTime: + return 27 + case .hideReadTimeInfo: + return 28 + case .subscribeToPremium: + return 29 + case .subscribeToPremiumInfo: + return 30 } } @@ -379,6 +410,30 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { } else { return false } + case let .hideReadTime(lhsTheme, lhsText, lhsValue): + if case let .hideReadTime(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + return true + } else { + return false + } + case let .hideReadTimeInfo(lhsTheme, lhsText): + if case let .hideReadTimeInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } + case let .subscribeToPremium(lhsTheme, lhsText): + if case let .subscribeToPremium(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } + case let .subscribeToPremiumInfo(lhsTheme, lhsText): + if case let .subscribeToPremiumInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } } } @@ -480,6 +535,18 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { case let .publicPhotoInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in }) + case let .hideReadTime(_, text, value): + return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + arguments.updateHideReadTime?(value) + }) + case let .hideReadTimeInfo(_, text): + return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) + case let .subscribeToPremium(_, text): + return ItemListPeerActionItem(presentationData: presentationData, icon: nil, title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { + arguments.openPremiumIntro() + }) + case let .subscribeToPremiumInfo(_, text): + return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) } } } @@ -500,10 +567,11 @@ private struct SelectivePrivacySettingsControllerState: Equatable { let callIntegrationAvailable: Bool? let callIntegrationEnabled: Bool? let phoneDiscoveryEnabled: Bool? - + let hideReadTimeEnabled: Bool? + let uploadedPhoto: UIImage? - init(setting: SelectivePrivacySettingType, enableFor: [EnginePeer.Id: SelectivePrivacyPeer], disableFor: [EnginePeer.Id: SelectivePrivacyPeer], enableForCloseFriends: Bool, saving: Bool, callDataSaving: VoiceCallDataSaving?, callP2PMode: SelectivePrivacySettingType?, callP2PEnableFor: [EnginePeer.Id: SelectivePrivacyPeer]?, callP2PDisableFor: [EnginePeer.Id: SelectivePrivacyPeer]?, callP2PEnableForCloseFriends: Bool?, callIntegrationAvailable: Bool?, callIntegrationEnabled: Bool?, phoneDiscoveryEnabled: Bool?, uploadedPhoto: UIImage?) { + init(setting: SelectivePrivacySettingType, enableFor: [EnginePeer.Id: SelectivePrivacyPeer], disableFor: [EnginePeer.Id: SelectivePrivacyPeer], enableForCloseFriends: Bool, saving: Bool, callDataSaving: VoiceCallDataSaving?, callP2PMode: SelectivePrivacySettingType?, callP2PEnableFor: [EnginePeer.Id: SelectivePrivacyPeer]?, callP2PDisableFor: [EnginePeer.Id: SelectivePrivacyPeer]?, callP2PEnableForCloseFriends: Bool?, callIntegrationAvailable: Bool?, callIntegrationEnabled: Bool?, phoneDiscoveryEnabled: Bool?, hideReadTimeEnabled: Bool?, uploadedPhoto: UIImage?) { self.setting = setting self.enableFor = enableFor self.disableFor = disableFor @@ -517,6 +585,7 @@ private struct SelectivePrivacySettingsControllerState: Equatable { self.callIntegrationAvailable = callIntegrationAvailable self.callIntegrationEnabled = callIntegrationEnabled self.phoneDiscoveryEnabled = phoneDiscoveryEnabled + self.hideReadTimeEnabled = hideReadTimeEnabled self.uploadedPhoto = uploadedPhoto } @@ -560,6 +629,9 @@ private struct SelectivePrivacySettingsControllerState: Equatable { if lhs.phoneDiscoveryEnabled != rhs.phoneDiscoveryEnabled { return false } + if lhs.hideReadTimeEnabled != rhs.hideReadTimeEnabled { + return false + } if lhs.uploadedPhoto !== rhs.uploadedPhoto { return false } @@ -568,52 +640,57 @@ private struct SelectivePrivacySettingsControllerState: Equatable { } func withUpdatedSetting(_ setting: SelectivePrivacySettingType) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedEnableFor(_ enableFor: [EnginePeer.Id: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedDisableFor(_ disableFor: [EnginePeer.Id: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedEnableForCloseFriends(_ enableForCloseFriends: Bool) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedSaving(_ saving: Bool) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedCallP2PMode(_ mode: SelectivePrivacySettingType) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: mode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: mode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedCallP2PEnableFor(_ enableFor: [EnginePeer.Id: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: enableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: enableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedCallP2PDisableFor(_ disableFor: [EnginePeer.Id: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: disableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: disableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedCallP2PEnableForCloseFriends(_ callP2PEnableForCloseFriends: Bool) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedCallsIntegrationEnabled(_ enabled: Bool) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: enabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: enabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedPhoneDiscoveryEnabled(_ phoneDiscoveryEnabled: Bool) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: phoneDiscoveryEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedUploadedPhoto(_ uploadedPhoto: UIImage?) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, uploadedPhoto: uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: uploadedPhoto) } + + func withUpdatedHideReadTimeEnabled(_ hideReadTimeEnabled: Bool) -> SelectivePrivacySettingsControllerState { + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) + } + } private func selectivePrivacySettingsControllerEntries(presentationData: PresentationData, kind: SelectivePrivacySettingsKind, state: SelectivePrivacySettingsControllerState, peerName: String, phoneNumber: String, peer: EnginePeer?, publicPhoto: TelegramMediaImage?) -> [SelectivePrivacySettingsEntry] { @@ -769,6 +846,17 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present entries.append(.publicPhotoInfo(presentationData.theme, presentationData.strings.Privacy_ProfilePhoto_PublicPhotoInfo)) } + if case .presence = kind, let peer { + //TODO:localize + entries.append(.hideReadTime(presentationData.theme, "Hide Read Time", state.hideReadTimeEnabled == true)) + entries.append(.hideReadTimeInfo(presentationData.theme, "Do not show the time when you read a message to people you hid your last seen from.")) + + if !peer.isPremium { + entries.append(.subscribeToPremium(presentationData.theme, "Subscribe to Telegram Premium")) + entries.append(.subscribeToPremiumInfo(presentationData.theme, "If you subscribe to Telegram Premium, you will still see other users' last seen and read time even if you hid yours from them (unless they specifically restricted it).")) + } + } + return entries } @@ -780,9 +868,10 @@ func selectivePrivacySettingsController( phoneDiscoveryEnabled: Bool? = nil, voipConfiguration: VoipConfiguration? = nil, callIntegrationAvailable: Bool? = nil, + globalSettings: GlobalPrivacySettings? = nil, requestPublicPhotoSetup: ((@escaping (UIImage?) -> Void) -> Void)? = nil, requestPublicPhotoRemove: ((@escaping () -> Void) -> Void)? = nil, - updated: @escaping (SelectivePrivacySettings, (SelectivePrivacySettings, VoiceCallSettings)?, Bool?) -> Void + updated: @escaping (SelectivePrivacySettings, (SelectivePrivacySettings, VoiceCallSettings)?, Bool?, GlobalPrivacySettings?) -> Void ) -> ViewController { let strings = context.sharedContext.currentPresentationData.with { $0 }.strings @@ -817,7 +906,8 @@ func selectivePrivacySettingsController( } } - let initialState = SelectivePrivacySettingsControllerState(setting: SelectivePrivacySettingType(current), enableFor: initialEnableFor, disableFor: initialDisableFor, enableForCloseFriends: initialEnableForCloseFriends, saving: false, callDataSaving: callSettings?.1.dataSaving, callP2PMode: callSettings != nil ? SelectivePrivacySettingType(callSettings!.0) : nil, callP2PEnableFor: initialCallP2PEnableFor, callP2PDisableFor: initialCallP2PDisableFor, callP2PEnableForCloseFriends: initialCallEnableForCloseFriends, callIntegrationAvailable: callIntegrationAvailable, callIntegrationEnabled: callSettings?.1.enableSystemIntegration, phoneDiscoveryEnabled: phoneDiscoveryEnabled, uploadedPhoto: nil) + //TODO:replace hideReadTimeEnabled with actual value + let initialState = SelectivePrivacySettingsControllerState(setting: SelectivePrivacySettingType(current), enableFor: initialEnableFor, disableFor: initialDisableFor, enableForCloseFriends: initialEnableForCloseFriends, saving: false, callDataSaving: callSettings?.1.dataSaving, callP2PMode: callSettings != nil ? SelectivePrivacySettingType(callSettings!.0) : nil, callP2PEnableFor: initialCallP2PEnableFor, callP2PDisableFor: initialCallP2PDisableFor, callP2PEnableForCloseFriends: initialCallEnableForCloseFriends, callIntegrationAvailable: callIntegrationAvailable, callIntegrationEnabled: callSettings?.1.enableSystemIntegration, phoneDiscoveryEnabled: phoneDiscoveryEnabled, hideReadTimeEnabled: globalSettings?.keepArchivedFolders, uploadedPhoto: nil) let statePromise = ValuePromise(initialState, ignoreRepeated: true) let stateValue = Atomic(value: initialState) @@ -1071,6 +1161,13 @@ func selectivePrivacySettingsController( return state.withUpdatedUploadedPhoto(nil) } }) + }, updateHideReadTime: { value in + updateState { state in + return state.withUpdatedHideReadTimeEnabled(value) + } + }, openPremiumIntro: { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .presence, forceDark: false, dismissed: nil) + pushControllerImpl?(controller, true) }) let peer = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) @@ -1129,6 +1226,7 @@ func selectivePrivacySettingsController( let callDataSaving: VoiceCallDataSaving? let callIntegrationEnabled: Bool? let phoneDiscoveryEnabled: Bool? + let hideReadTimeEnabled: Bool? } var appliedSettings: AppliedSettings? @@ -1140,6 +1238,8 @@ func selectivePrivacySettingsController( var phoneDiscoveryEnabled: Bool? var callDataSaving: VoiceCallDataSaving? var callIntegrationEnabled: Bool? + var hideReadTimeEnabled: Bool? + updateState { state in wasSaving = state.saving callDataSaving = state.callDataSaving @@ -1157,6 +1257,10 @@ func selectivePrivacySettingsController( phoneDiscoveryEnabled = value } + if case .presence = kind, let value = state.hideReadTimeEnabled { + hideReadTimeEnabled = value + } + if case .voiceCalls = kind, let callP2PMode = state.callP2PMode, let disableFor = state.callP2PDisableFor, let enableFor = state.callP2PEnableFor, let enableForCloseFriends = state.callP2PEnableForCloseFriends { switch callP2PMode { case .everybody: @@ -1172,7 +1276,7 @@ func selectivePrivacySettingsController( } if let settings = settings, !wasSaving { - let settingsToApply = AppliedSettings(settings: settings, callP2PSettings: callP2PSettings, callDataSaving: callDataSaving, callIntegrationEnabled: callIntegrationEnabled, phoneDiscoveryEnabled: phoneDiscoveryEnabled) + let settingsToApply = AppliedSettings(settings: settings, callP2PSettings: callP2PSettings, callDataSaving: callDataSaving, callIntegrationEnabled: callIntegrationEnabled, phoneDiscoveryEnabled: phoneDiscoveryEnabled, hideReadTimeEnabled: hideReadTimeEnabled) if appliedSettings == settingsToApply { return } @@ -1208,14 +1312,27 @@ func selectivePrivacySettingsController( updatePhoneDiscoverySignal = context.engine.privacy.updatePhoneNumberDiscovery(value: phoneDiscoveryEnabled) } - let _ = (combineLatest(updateSettingsSignal, updateCallP2PSettingsSignal, updatePhoneDiscoverySignal) + var updateGlobalSettingsSignal: Signal = Signal.complete() + var updatedGlobalSettings: GlobalPrivacySettings? + if let updateHideReadTime = arguments.updateHideReadTime, let globalSettings { + //TODO:update global settings + let _ = updateHideReadTime + updatedGlobalSettings = GlobalPrivacySettings(automaticallyArchiveAndMuteNonContacts: globalSettings.automaticallyArchiveAndMuteNonContacts, keepArchivedUnmuted: globalSettings.keepArchivedUnmuted, keepArchivedFolders: globalSettings.keepArchivedFolders) + if let updatedGlobalSettings { + updateGlobalSettingsSignal = context.engine.privacy.updateGlobalPrivacySettings(settings: updatedGlobalSettings) + } + } + + let _ = (combineLatest(updateSettingsSignal, updateCallP2PSettingsSignal, updatePhoneDiscoverySignal, updateGlobalSettingsSignal) |> deliverOnMainQueue).start(completed: { }) - if case .voiceCalls = kind, let dataSaving = callDataSaving, let callP2PSettings = callP2PSettings, let systemIntegrationEnabled = callIntegrationEnabled { - updated(settings, (callP2PSettings, VoiceCallSettings(dataSaving: dataSaving, enableSystemIntegration: systemIntegrationEnabled)), phoneDiscoveryEnabled) + if case .presence = kind { + updated(settings, nil, phoneDiscoveryEnabled, updateGlobalSettingsSignal) + } else if case .voiceCalls = kind, let dataSaving = callDataSaving, let callP2PSettings = callP2PSettings, let systemIntegrationEnabled = callIntegrationEnabled { + updated(settings, (callP2PSettings, VoiceCallSettings(dataSaving: dataSaving, enableSystemIntegration: systemIntegrationEnabled)), phoneDiscoveryEnabled, nil) } else { - updated(settings, nil, phoneDiscoveryEnabled) + updated(settings, nil, phoneDiscoveryEnabled, nil) } } } diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift index 8d4f0180be..f6dc8b4ece 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift @@ -597,7 +597,7 @@ private func privacySearchableItems(context: AccountContext, privacySettings: Ac current = info.bio } - present(.push, selectivePrivacySettingsController(context: context, kind: kind, current: current, callSettings: callSettings != nil ? (info.voiceCallsP2P, callSettings!.0) : nil, voipConfiguration: callSettings?.1, callIntegrationAvailable: CallKitIntegration.isAvailable, updated: { updated, updatedCallSettings, _ in + present(.push, selectivePrivacySettingsController(context: context, kind: kind, current: current, callSettings: callSettings != nil ? (info.voiceCallsP2P, callSettings!.0) : nil, voipConfiguration: callSettings?.1, callIntegrationAvailable: CallKitIntegration.isAvailable, updated: { updated, updatedCallSettings, _, _ in if let (_, updatedCallSettings) = updatedCallSettings { let _ = updateVoiceCallSettingsSettingsInteractively(accountManager: context.sharedContext.accountManager, { _ in return updatedCallSettings diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index e7a561f5ce..ad4b538a5b 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1945,6 +1945,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSource = .similarChannels case .wallpapers: mappedSource = .wallpapers + case .presence: + mappedSource = .presence } let controller = PremiumIntroScreen(context: context, modal: modal, source: mappedSource, forceDark: forceDark) controller.wasDismissed = dismissed From 049c7f9c0c505359d090541dd28ecdeccb090622 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 5 Jan 2024 00:33:37 +0400 Subject: [PATCH 4/4] Fix build --- .../SelectivePrivacySettingsController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index 1f0394522a..f5f440f7a3 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -1328,7 +1328,7 @@ func selectivePrivacySettingsController( }) if case .presence = kind { - updated(settings, nil, phoneDiscoveryEnabled, updateGlobalSettingsSignal) + updated(settings, nil, phoneDiscoveryEnabled, updatedGlobalSettings) } else if case .voiceCalls = kind, let dataSaving = callDataSaving, let callP2PSettings = callP2PSettings, let systemIntegrationEnabled = callIntegrationEnabled { updated(settings, (callP2PSettings, VoiceCallSettings(dataSaving: dataSaving, enableSystemIntegration: systemIntegrationEnabled)), phoneDiscoveryEnabled, nil) } else {