Implement forward and copy restriction

This commit is contained in:
Ilya Laktyushin 2021-11-03 13:35:57 +04:00
parent 7b3d1c6f59
commit 003d263ebd
6 changed files with 89 additions and 62 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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