From 003d263ebd2570668b362e3f239d16600f1a855c Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 3 Nov 2021 13:35:57 +0400 Subject: [PATCH] Implement forward and copy restriction --- .../ChatItemGalleryFooterContentNode.swift | 2 +- .../Sources/Utils/MessageUtils.swift | 8 ++ .../ChatInterfaceStateContextMenus.swift | 77 +++++++++++-------- .../Sources/ChatMessageBubbleItemNode.swift | 48 ++++++------ .../ChatMessageSelectionInputPanelNode.swift | 5 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 11 ++- 6 files changed, 89 insertions(+), 62 deletions(-) diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 1b15fd04fc..12cfe1b43e 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -576,7 +576,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll self.currentMessage = message let canDelete: Bool - var canShare = !message.containsSecretMedia + var canShare = !message.containsSecretMedia && !message.isCopyProtected() var canFullscreen = false diff --git a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift index 316d17ca13..7a8f06d3d2 100644 --- a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift @@ -238,6 +238,14 @@ public extension Message { return false } } + + func isCopyProtected() -> Bool { + if let channel = self.peers[self.id.peerId] as? TelegramChannel, case let .broadcast(flags) = channel.info, flags.flags.contains(.copyProtectionEnabled) && channel.adminRights == nil { + return true + } else { + return false + } + } } public extension Message { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 1f28aff460..5817a35cab 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -376,36 +376,40 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState actions.append(.separator) - actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopy, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - let copyTextWithEntities = { - var messageEntities: [MessageTextEntity]? - var restrictedText: String? - for attribute in message.attributes { - if let attribute = attribute as? TextEntitiesMessageAttribute { - messageEntities = attribute.entities + if message.isCopyProtected() { + + } else { + actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopy, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + let copyTextWithEntities = { + var messageEntities: [MessageTextEntity]? + var restrictedText: String? + for attribute in message.attributes { + if let attribute = attribute as? TextEntitiesMessageAttribute { + messageEntities = attribute.entities + } + if let attribute = attribute as? RestrictedContentMessageAttribute { + restrictedText = attribute.platformText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) ?? "" + } } - if let attribute = attribute as? RestrictedContentMessageAttribute { - restrictedText = attribute.platformText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) ?? "" + + if let restrictedText = restrictedText { + storeMessageTextInPasteboard(restrictedText, entities: nil) + } else { + storeMessageTextInPasteboard(message.text, entities: messageEntities) } + + Queue.mainQueue().after(0.2, { + let content: UndoOverlayContent = .copy(text: chatPresentationInterfaceState.strings.Conversation_MessageCopied) + controllerInteraction.displayUndo(content) + }) } - if let restrictedText = restrictedText { - storeMessageTextInPasteboard(restrictedText, entities: nil) - } else { - storeMessageTextInPasteboard(message.text, entities: messageEntities) - } - - Queue.mainQueue().after(0.2, { - let content: UndoOverlayContent = .copy(text: chatPresentationInterfaceState.strings.Conversation_MessageCopied) - controllerInteraction.displayUndo(content) - }) - } - - copyTextWithEntities() - f(.default) - }))) + copyTextWithEntities() + f(.default) + }))) + } if let author = message.author, let addressName = author.addressName { let link = "https://t.me/\(addressName)" @@ -682,7 +686,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState resourceAvailable = false } - if !messages[0].text.isEmpty || resourceAvailable || diceEmoji != nil { + if (!messages[0].text.isEmpty || resourceAvailable || diceEmoji != nil) && !messages[0].isCopyProtected() { let message = messages[0] var isExpired = false for media in message.media { @@ -763,6 +767,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState }))) } } + if resourceAvailable, !message.containsSecretMedia { var mediaReference: AnyMediaReference? var isVideo = false @@ -1036,12 +1041,16 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } if data.messageActions.options.contains(.forward) { - actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuForward, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - interfaceInteraction.forwardMessages(selectAll ? messages : [message]) - f(.dismissWithoutContent) - }))) + if let channel = message.peers[message.id.peerId] as? TelegramChannel, case let .broadcast(flags) = channel.info, flags.flags.contains(.copyProtectionEnabled) && channel.adminRights == nil { + + } else { + actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuForward, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + interfaceInteraction.forwardMessages(selectAll ? messages : [message]) + f(.dismissWithoutContent) + }))) + } } if data.messageActions.options.contains(.report) { @@ -1370,7 +1379,7 @@ func chatAvailableMessageActionsImpl(postbox: Postbox, accountPeerId: PeerId, me } } if !message.containsSecretMedia && !isAction { - if message.id.peerId.namespace != Namespaces.Peer.SecretChat { + if message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.isCopyProtected() { if !(message.flags.isSending || message.flags.contains(.Failed)) { optionsMap[id]!.insert(.forward) } diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 11e6b6bec4..20b6e7103d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -1037,87 +1037,91 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp()) - var needShareButton = false + var needsShareButton = false if case .pinnedMessages = item.associatedData.subject { - needShareButton = true + needsShareButton = true for media in item.message.media { if let _ = media as? TelegramMediaExpiredContent { - needShareButton = false + needsShareButton = false break } } } else if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.effectiveTopId == item.message.id { - needShareButton = false + needsShareButton = false allowFullWidth = true } else if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) { - needShareButton = false + needsShareButton = false } else if item.message.id.peerId == item.context.account.peerId { if let _ = sourceReference { - needShareButton = true + needsShareButton = true } } else if item.message.id.peerId.isReplies { - needShareButton = false + needsShareButton = false } else if item.message.effectivelyIncoming(item.context.account.peerId) { if let _ = sourceReference { - needShareButton = true + needsShareButton = true } if let peer = item.message.peers[item.message.id.peerId] { if let channel = peer as? TelegramChannel { if case .broadcast = channel.info { - needShareButton = true + needsShareButton = true } } } if let info = item.message.forwardInfo { if let author = info.author as? TelegramUser, let _ = author.botInfo, !item.message.media.isEmpty && !(item.message.media.first is TelegramMediaAction) { - needShareButton = true + needsShareButton = true } else if let author = info.author as? TelegramChannel, case .broadcast = author.info { - needShareButton = true + needsShareButton = true } } - if !needShareButton, let author = item.message.author as? TelegramUser, let _ = author.botInfo, !item.message.media.isEmpty && !(item.message.media.first is TelegramMediaAction) { - needShareButton = true + if !needsShareButton, let author = item.message.author as? TelegramUser, let _ = author.botInfo, !item.message.media.isEmpty && !(item.message.media.first is TelegramMediaAction) { + needsShareButton = true } - if !needShareButton { + if !needsShareButton { loop: for media in item.message.media { if media is TelegramMediaGame || media is TelegramMediaInvoice { - needShareButton = true + needsShareButton = true break loop } else if let media = media as? TelegramMediaWebpage, case .Loaded = media.content { - needShareButton = true + needsShareButton = true break loop } } } else { loop: for media in item.message.media { if media is TelegramMediaAction { - needShareButton = false + needsShareButton = false break loop } } } + + if item.message.isCopyProtected() { + needsShareButton = false + } } if isPreview { - needShareButton = false + needsShareButton = false } let isAd = item.content.firstMessage.adAttribute != nil if isAd { - needShareButton = false + needsShareButton = false } var tmpWidth: CGFloat if allowFullWidth { tmpWidth = baseWidth - if needShareButton || isAd { + if needsShareButton || isAd { tmpWidth -= 38.0 } } else { tmpWidth = layoutConstants.bubble.maximumWidthFill.widthFor(baseWidth) - if (needShareButton || isAd) && tmpWidth + 32.0 > baseWidth { + if (needsShareButton || isAd) && tmpWidth + 32.0 > baseWidth { tmpWidth = baseWidth - 32.0 } } @@ -2031,7 +2035,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode contentContainerNodeFrames: contentContainerNodeFrames, mosaicStatusOrigin: mosaicStatusOrigin, mosaicStatusSizeAndApply: mosaicStatusSizeAndApply, - needsShareButton: needShareButton + needsShareButton: needsShareButton ) }) } diff --git a/submodules/TelegramUI/Sources/ChatMessageSelectionInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatMessageSelectionInputPanelNode.swift index 34fdd0cbb6..17ad4b6c85 100644 --- a/submodules/TelegramUI/Sources/ChatMessageSelectionInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageSelectionInputPanelNode.swift @@ -70,7 +70,6 @@ final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { self.forwardButton.accessibilityLabel = strings.VoiceOver_MessageContextForward self.shareButton = HighlightableButtonNode(pointerStyle: .default) - self.shareButton.isEnabled = false self.shareButton.isAccessibilityElement = true self.shareButton.accessibilityLabel = strings.VoiceOver_MessageContextShare @@ -95,6 +94,7 @@ final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { self.addSubnode(self.separatorNode) self.forwardButton.isEnabled = false + self.shareButton.isEnabled = false self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), forControlEvents: .touchUpInside) self.reportButton.addTarget(self, action: #selector(self.reportButtonPressed), forControlEvents: .touchUpInside) @@ -116,6 +116,8 @@ final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { self.reportButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionReport"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) + self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: theme.chat.inputPanel.panelControlAccentColor), for: [.normal]) + self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled]) self.separatorNode.backgroundColor = theme.chat.inputPanel.panelSeparatorColor } @@ -149,7 +151,6 @@ final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { self.deleteButton.isEnabled = false self.reportButton.isEnabled = false self.forwardButton.isEnabled = actions.options.contains(.forward) - self.shareButton.isEnabled = false if self.peerMedia { self.deleteButton.isEnabled = !actions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 49da25c706..30ec44dd6d 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -1191,6 +1191,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr case groupLocation case peerPublicSettings case peerSettings + case peerAdditionalSettings case peerActions } @@ -1278,7 +1279,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr })) items[.peerSettings]!.append(PeerInfoScreenCommentItem(id: ItemSignMessagesHelp, text: presentationData.strings.Channel_SignMessages_Help)) - items[.peerSettings]!.append(PeerInfoScreenSwitchItem(id: ItemCopyProtection, text: "Disable Forwards", value: messagesCopyProtection, icon: UIImage(bundleImageName: "Chat/Info/GroupSignIcon"), toggled: { value in + items[.peerAdditionalSettings]!.append(PeerInfoScreenSwitchItem(id: ItemCopyProtection, text: "Disable Forwards", value: messagesCopyProtection, icon: UIImage(bundleImageName: "Chat/Info/GroupSignIcon"), toggled: { value in interaction.editingToggleChannelMessageCopyProtection(value) })) } @@ -1851,7 +1852,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate }))) } - if message.id.peerId.namespace != Namespaces.Peer.SecretChat { + if let channel = message.peers[message.id.peerId] as? TelegramChannel, case let .broadcast(flags) = channel.info, flags.flags.contains(.copyProtectionEnabled) && channel.adminRights == nil { + + } else if message.id.peerId.namespace != Namespaces.Peer.SecretChat { 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 { @@ -1987,7 +1990,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate }) }))) - if message.id.peerId.namespace != Namespaces.Peer.SecretChat { + if message.isCopyProtected() { + + } else if message.id.peerId.namespace != Namespaces.Peer.SecretChat { items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { c, f in c.dismiss(completion: { if let strongSelf = self {