diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 6eb7f425c6..1424978e28 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7050,9 +7050,19 @@ Sorry for the inconvenience."; "Chat.JumpToDate" = "Jump to Date"; -"Channel.AdminLog.MessageToggleNoForwardsOn" = "%@ enabled no-forwards"; -"Channel.AdminLog.MessageToggleNoForwardsOff" = "%@ disabled no-forwards"; - "VoiceChat.DiscussionGroup" = "discussion group"; "Group.Edit.PrivatePublicLinkAlert" = "Please note that if you choose a public link for your group, anyone will be able to find it in search and join.\n\nDo not create this link if you want your group to stay private."; + +"Conversation.CopyProtectionInfoGroup" = "Admins restricted members to copy or forward content from this group."; +"Conversation.CopyProtectionInfoChannel" = "Admins restricted members to copy or forward content from this channel."; + +"Channel.AdminLog.MessageToggleNoForwardsOn" = "%@ restricted message forwarding"; +"Channel.AdminLog.MessageToggleNoForwardsOff" = "%@ allowed message forwarding"; + +"Group.Setup.ForwardingGroupTitle" = "Forwarding From This Group"; +"Group.Setup.ForwardingChannelTitle" = "Forwarding From This Channel"; +"Group.Setup.ForwardingEnabled" = "Allow Forwarding"; +"Group.Setup.ForwardingDisabled" = "Restrict Forwarding"; +"Group.Setup.ForwardingGroupInfo" = "Participants can forward messages from this group and save media files."; +"Group.Setup.ForwardingChannelInfo" = "Participants can forward messages from this channel and save media files."; diff --git a/submodules/ContextUI/Sources/ContextActionsContainerNode.swift b/submodules/ContextUI/Sources/ContextActionsContainerNode.swift index 8510ecdd80..80212b6b9c 100644 --- a/submodules/ContextUI/Sources/ContextActionsContainerNode.swift +++ b/submodules/ContextUI/Sources/ContextActionsContainerNode.swift @@ -351,6 +351,7 @@ private final class InnerTextSelectionTipContainerNode: ASDisplayNode { self.presentationData = presentationData self.textNode = TextNode() + var icon: UIImage? switch tip { case .textSelection: var rawText = self.presentationData.strings.ChatContextMenu_TextSelectionTip @@ -362,15 +363,21 @@ private final class InnerTextSelectionTipContainerNode: ASDisplayNode { self.text = rawText self.targetSelectionIndex = 1 } + icon = UIImage(bundleImageName: "Chat/Context Menu/Tip") case .messageViewsPrivacy: self.text = self.presentationData.strings.ChatContextMenu_MessageViewsPrivacyTip self.targetSelectionIndex = nil + icon = UIImage(bundleImageName: "Chat/Context Menu/Tip") + case let .messageCopyProtection(isChannel): + self.text = isChannel ? self.presentationData.strings.Conversation_CopyProtectionInfoChannel : self.presentationData.strings.Conversation_CopyProtectionInfoGroup + self.targetSelectionIndex = nil + icon = UIImage(bundleImageName: "Chat/Context Menu/ReportCopyright") } self.iconNode = ASImageNode() self.iconNode.displaysAsynchronously = false self.iconNode.displayWithoutProcessing = true - self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Tip"), color: presentationData.theme.contextMenu.primaryColor) + self.iconNode.image = generateTintedImage(image: icon, color: presentationData.theme.contextMenu.primaryColor) super.init() diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index b096fa94c4..f8dadf79d1 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -1828,6 +1828,7 @@ public final class ContextController: ViewController, StandalonePresentableContr public enum Tip { case textSelection case messageViewsPrivacy + case messageCopyProtection(isChannel: Bool) } public final class ActionsHeight { diff --git a/submodules/GalleryUI/Sources/GalleryControllerNode.swift b/submodules/GalleryUI/Sources/GalleryControllerNode.swift index a998a9f8d8..0c1152e64c 100644 --- a/submodules/GalleryUI/Sources/GalleryControllerNode.swift +++ b/submodules/GalleryUI/Sources/GalleryControllerNode.swift @@ -329,15 +329,18 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture open func animateIn(animateContent: Bool, useSimpleAnimation: Bool) { let duration: Double = animateContent ? 0.2 : 0.3 - - self.backgroundNode.backgroundColor = self.backgroundNode.backgroundColor?.withAlphaComponent(0.0) + let fadeDuration: Double = 0.2 + + let backgroundColor = self.backgroundNode.backgroundColor ?? .black + self.statusBar?.alpha = 0.0 self.navigationBar?.alpha = 0.0 self.footerNode.alpha = 0.0 self.currentThumbnailContainerNode?.alpha = 0.0 - UIView.animate(withDuration: duration, animations: { - self.backgroundNode.backgroundColor = self.backgroundNode.backgroundColor?.withAlphaComponent(1.0) + self.backgroundNode.layer.animate(from: backgroundColor.withAlphaComponent(0.0).cgColor, to: backgroundColor.cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: fadeDuration) + + UIView.animate(withDuration: fadeDuration, delay: 0.0, options: [.curveLinear], animations: { if !self.areControlsHidden { self.statusBar?.alpha = 1.0 self.navigationBar?.alpha = 1.0 diff --git a/submodules/LocationUI/Sources/LocationInfoListItem.swift b/submodules/LocationUI/Sources/LocationInfoListItem.swift index 10b0aa3f90..869b49768d 100644 --- a/submodules/LocationUI/Sources/LocationInfoListItem.swift +++ b/submodules/LocationUI/Sources/LocationInfoListItem.swift @@ -279,34 +279,49 @@ final class LocationInfoListItemNode: ListViewItemNode { let iconNodeFrame = CGRect(origin: CGPoint(x: params.leftInset + inset, y: 10.0), size: CGSize(width: iconSize, height: iconSize)) strongSelf.venueIconNode.frame = iconNodeFrame - if case let .ready(drivingTime) = item.drivingTime { - strongSelf.drivingButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: drivingTime, format: { $0 }) + var directionsWidth: CGFloat = 93.0 + + if item.drivingTime == .unknown && item.transitTime == .unknown && item.walkingTime == .unknown { + strongSelf.drivingButtonNode?.icon = nil + strongSelf.drivingButtonNode?.title = item.presentationData.strings.Map_GetDirections + if let drivingButtonNode = strongSelf.drivingButtonNode { + let buttonSize = drivingButtonNode.sizeThatFits(contentSize) + directionsWidth = buttonSize.width + } if let previousDrivingTime = currentItem?.drivingTime, case .calculating = previousDrivingTime { strongSelf.drivingButtonNode?.alpha = 1.0 strongSelf.drivingButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - } - - if case let .ready(transitTime) = item.transitTime { - strongSelf.transitButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: transitTime, format: { $0 }) + } else { + if case let .ready(drivingTime) = item.drivingTime { + strongSelf.drivingButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: drivingTime, format: { $0 }) + + if let previousDrivingTime = currentItem?.drivingTime, case .calculating = previousDrivingTime { + strongSelf.drivingButtonNode?.alpha = 1.0 + strongSelf.drivingButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } - if let previousTransitTime = currentItem?.transitTime, case .calculating = previousTransitTime { - strongSelf.transitButtonNode?.alpha = 1.0 - strongSelf.transitButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + if case let .ready(transitTime) = item.transitTime { + strongSelf.transitButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: transitTime, format: { $0 }) + + if let previousTransitTime = currentItem?.transitTime, case .calculating = previousTransitTime { + strongSelf.transitButtonNode?.alpha = 1.0 + strongSelf.transitButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + + if case let .ready(walkingTime) = item.walkingTime { + strongSelf.walkingButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: walkingTime, format: { $0 }) + + if let previousWalkingTime = currentItem?.walkingTime, case .calculating = previousWalkingTime { + strongSelf.walkingButtonNode?.alpha = 1.0 + strongSelf.walkingButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } } } - if case let .ready(walkingTime) = item.walkingTime { - strongSelf.walkingButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: walkingTime, format: { $0 }) - - if let previousWalkingTime = currentItem?.walkingTime, case .calculating = previousWalkingTime { - strongSelf.walkingButtonNode?.alpha = 1.0 - strongSelf.walkingButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - } - - let directionsWidth: CGFloat = 93.0 let directionsSpacing: CGFloat = 8.0 if case .calculating = item.drivingTime, case .calculating = item.transitTime, case .calculating = item.walkingTime { diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index f022eff88a..19e682416f 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -34,8 +34,9 @@ private final class ChannelVisibilityControllerArguments { let linkContextAction: (ASDisplayNode, ContextGesture?) -> Void let manageInviteLinks: () -> Void let openLink: (ExportedInvitation) -> Void + let toggleForwarding: (Bool) -> Void - init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, revokePeerId: @escaping (PeerId) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, linkContextAction: @escaping (ASDisplayNode, ContextGesture?) -> Void, manageInviteLinks: @escaping () -> Void, openLink: @escaping (ExportedInvitation) -> Void) { + init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, revokePeerId: @escaping (PeerId) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, linkContextAction: @escaping (ASDisplayNode, ContextGesture?) -> Void, manageInviteLinks: @escaping () -> Void, openLink: @escaping (ExportedInvitation) -> Void, toggleForwarding: @escaping (Bool) -> Void) { self.context = context self.updateCurrentType = updateCurrentType self.updatePublicLinkText = updatePublicLinkText @@ -47,6 +48,7 @@ private final class ChannelVisibilityControllerArguments { self.linkContextAction = linkContextAction self.manageInviteLinks = manageInviteLinks self.openLink = openLink + self.toggleForwarding = toggleForwarding } } @@ -54,6 +56,7 @@ private enum ChannelVisibilitySection: Int32 { case type case link case linkActions + case forwarding } private enum ChannelVisibilityEntryTag: ItemListItemTag { @@ -90,6 +93,11 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { case existingLinksInfo(PresentationTheme, String) case existingLinkPeerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, ItemListPeerItemEditing, Bool) + case forwardingHeader(PresentationTheme, String) + case forwardingEnabled(PresentationTheme, String, Bool) + case forwardingDisabled(PresentationTheme, String, Bool) + case forwardingInfo(PresentationTheme, String) + var section: ItemListSectionId { switch self { case .typeHeader, .typePublic, .typePrivate, .typeInfo: @@ -100,6 +108,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { return ChannelVisibilitySection.linkActions.rawValue case .existingLinksInfo, .existingLinkPeerItem: return ChannelVisibilitySection.link.rawValue + case .forwardingHeader, .forwardingEnabled, .forwardingDisabled, .forwardingInfo: + return ChannelVisibilitySection.forwarding.rawValue } } @@ -137,6 +147,14 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { return 1000 case .privateLinkManageInfo: return 1001 + case .forwardingHeader: + return 1002 + case .forwardingEnabled: + return 1003 + case .forwardingDisabled: + return 1004 + case .forwardingInfo: + return 1005 } } @@ -262,6 +280,30 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { } else { return false } + case let .forwardingHeader(lhsTheme, lhsText): + if case let .forwardingHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } + case let .forwardingEnabled(lhsTheme, lhsText, lhsValue): + if case let .forwardingEnabled(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + return true + } else { + return false + } + case let .forwardingDisabled(lhsTheme, lhsText, lhsValue): + if case let .forwardingDisabled(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + return true + } else { + return false + } + case let .forwardingInfo(lhsTheme, lhsText): + if case let .forwardingInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } } } @@ -359,6 +401,18 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { }, removePeer: { peerId in arguments.revokePeerId(peerId) }) + case let .forwardingHeader(_, title): + return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) + case let .forwardingEnabled(_, text, selected): + return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { + arguments.toggleForwarding(true) + }) + case let .forwardingDisabled(_, text, selected): + return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { + arguments.toggleForwarding(false) + }) + case let .forwardingInfo(_, text): + return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) } } } @@ -381,6 +435,7 @@ private struct ChannelVisibilityControllerState: Equatable { let revealedRevokePeerId: PeerId? let revokingPeerId: PeerId? let revokingPrivateLink: Bool + let forwardingEnabled: Bool? init() { self.selectedType = nil @@ -390,9 +445,10 @@ private struct ChannelVisibilityControllerState: Equatable { self.revealedRevokePeerId = nil self.revokingPeerId = nil self.revokingPrivateLink = false + self.forwardingEnabled = nil } - init(selectedType: CurrentChannelType?, editingPublicLinkText: String?, addressNameValidationStatus: AddressNameValidationStatus?, updatingAddressName: Bool, revealedRevokePeerId: PeerId?, revokingPeerId: PeerId?, revokingPrivateLink: Bool) { + init(selectedType: CurrentChannelType?, editingPublicLinkText: String?, addressNameValidationStatus: AddressNameValidationStatus?, updatingAddressName: Bool, revealedRevokePeerId: PeerId?, revokingPeerId: PeerId?, revokingPrivateLink: Bool, forwardingEnabled: Bool?) { self.selectedType = selectedType self.editingPublicLinkText = editingPublicLinkText self.addressNameValidationStatus = addressNameValidationStatus @@ -400,6 +456,7 @@ private struct ChannelVisibilityControllerState: Equatable { self.revealedRevokePeerId = revealedRevokePeerId self.revokingPeerId = revokingPeerId self.revokingPrivateLink = revokingPrivateLink + self.forwardingEnabled = forwardingEnabled } static func ==(lhs: ChannelVisibilityControllerState, rhs: ChannelVisibilityControllerState) -> Bool { @@ -424,35 +481,42 @@ private struct ChannelVisibilityControllerState: Equatable { if lhs.revokingPrivateLink != rhs.revokingPrivateLink { return false } + if lhs.forwardingEnabled != rhs.forwardingEnabled { + return false + } return true } func withUpdatedSelectedType(_ selectedType: CurrentChannelType?) -> ChannelVisibilityControllerState { - return ChannelVisibilityControllerState(selectedType: selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink) + return ChannelVisibilityControllerState(selectedType: selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled) } func withUpdatedEditingPublicLinkText(_ editingPublicLinkText: String?) -> ChannelVisibilityControllerState { - return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink) + return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled) } func withUpdatedAddressNameValidationStatus(_ addressNameValidationStatus: AddressNameValidationStatus?) -> ChannelVisibilityControllerState { - return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink) + return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled) } func withUpdatedUpdatingAddressName(_ updatingAddressName: Bool) -> ChannelVisibilityControllerState { - return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink) + return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled) } func withUpdatedRevealedRevokePeerId(_ revealedRevokePeerId: PeerId?) -> ChannelVisibilityControllerState { - return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink) + return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled) } func withUpdatedRevokingPeerId(_ revokingPeerId: PeerId?) -> ChannelVisibilityControllerState { - return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: revokingPeerId, revokingPrivateLink: self.revokingPrivateLink) + return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled) } func withUpdatedRevokingPrivateLink(_ revokingPrivateLink: Bool) -> ChannelVisibilityControllerState { - return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: revokingPrivateLink) + return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: revokingPrivateLink, forwardingEnabled: self.forwardingEnabled) + } + + func withUpdatedForwardingEnabled(_ forwardingEnabled: Bool) -> ChannelVisibilityControllerState { + return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: forwardingEnabled) } } @@ -484,6 +548,17 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa } } + let forwardingEnabled: Bool + if let enabled = state.forwardingEnabled { + forwardingEnabled = enabled + } else { + if peer.flags.contains(.copyProtectionEnabled) { + forwardingEnabled = false + } else { + forwardingEnabled = true + } + } + let currentAddressName: String if let current = state.editingPublicLinkText { currentAddressName = current @@ -633,6 +708,12 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa entries.append(.privateLinkManageInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo)) } } + + entries.append(.forwardingHeader(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_ForwardingGroupTitle.uppercased() : presentationData.strings.Group_Setup_ForwardingChannelTitle.uppercased())) + entries.append(.forwardingEnabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingEnabled, forwardingEnabled)) + entries.append(.forwardingDisabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingDisabled, !forwardingEnabled)) + entries.append(.forwardingInfo(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_ForwardingGroupInfo : presentationData.strings.Group_Setup_ForwardingChannelInfo)) + } else if let _ = view.peers[view.peerId] as? TelegramGroup { switch mode { case .privateLink: @@ -748,6 +829,18 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa } } } + + let forwardingEnabled: Bool + if let enabled = state.forwardingEnabled { + forwardingEnabled = enabled + } else { + forwardingEnabled = true + } + + entries.append(.forwardingHeader(presentationData.theme, presentationData.strings.Group_Setup_ForwardingGroupTitle.uppercased())) + entries.append(.forwardingEnabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingEnabled, forwardingEnabled)) + entries.append(.forwardingDisabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingEnabled, !forwardingEnabled)) + entries.append(.forwardingInfo(presentationData.theme, presentationData.strings.Group_Setup_ForwardingGroupInfo)) } return entries @@ -862,6 +955,9 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta let revokeLinkDisposable = MetaDisposable() actionsDisposable.add(revokeLinkDisposable) + let toggleCopyProtectionDisposable = MetaDisposable() + actionsDisposable.add(toggleCopyProtectionDisposable) + let arguments = ChannelVisibilityControllerArguments(context: context, updateCurrentType: { type in updateState { state in return state.withUpdatedSelectedType(type) @@ -1062,6 +1158,10 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta }, openLink: { invite in let controller = InviteLinkViewController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, invite: invite, invitationsContext: nil, revokedInvitationsContext: nil, importersContext: nil) pushControllerImpl?(controller) + }, toggleForwarding: { value in + updateState { state in + return state.withUpdatedForwardingEnabled(value) + } }) let peerView = context.account.viewTracker.peerView(peerId) @@ -1127,6 +1227,10 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta return state } + if let updatedCopyProtection = state.forwardingEnabled { + toggleCopyProtectionDisposable.set(context.engine.peers.toggleMessageCopyProtection(peerId: peerId, enabled: !updatedCopyProtection).start()) + } + if let updatedAddressNameValue = updatedAddressNameValue { let invokeAction: () -> Void = { updateState { state in @@ -1203,6 +1307,10 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta return state } + if let updatedCopyProtection = state.forwardingEnabled { + toggleCopyProtectionDisposable.set(context.engine.peers.toggleMessageCopyProtection(peerId: peerId, enabled: !updatedCopyProtection).start()) + } + if let updatedAddressNameValue = updatedAddressNameValue { let invokeAction: () -> Void = { updateState { state in diff --git a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift index ce348efa81..37a17a6faf 100644 --- a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift +++ b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift @@ -216,6 +216,11 @@ public final class SolidRoundedButtonNode: ASDisplayNode { } } + public func sizeThatFits(_ constrainedSize: CGSize) -> CGSize { + let titleSize = self.titleNode.updateLayout(constrainedSize) + return CGSize(width: titleSize.width + 20.0, height: self.buttonHeight) + } + public func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { return self.updateLayout(width: width, previousSubtitle: self.subtitle, transition: transition) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 9ec1f36a7d..266ee16fe8 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -280,7 +280,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private var clearCacheDisposable: MetaDisposable? private var bankCardDisposable: MetaDisposable? private var hasActiveGroupCallDisposable: Disposable? - + private var sendAsPeersDisposable: Disposable? + private let editingMessage = ValuePromise(nil, ignoreRepeated: true) private let startingBot = ValuePromise(false, ignoreRepeated: true) private let unblockingPeer = ValuePromise(false, ignoreRepeated: true) @@ -943,11 +944,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var tip: ContextController.Tip? if tip == nil { - let numberOfComponents = message.text.components(separatedBy: CharacterSet.whitespacesAndNewlines).count - let displayTextSelectionTip = numberOfComponents >= 3 && !message.text.isEmpty && chatTextSelectionTips < 3 && !message.isCopyProtected() - if displayTextSelectionTip { - let _ = ApplicationSpecificNotice.incrementChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager).start() - tip = .textSelection + if message.isCopyProtected() { + var isChannel = false + if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, case .broadcast = channel.info { + isChannel = true + } + tip = .messageCopyProtection(isChannel: isChannel) + } else { + let numberOfComponents = message.text.components(separatedBy: CharacterSet.whitespacesAndNewlines).count + let displayTextSelectionTip = numberOfComponents >= 3 && !message.text.isEmpty && chatTextSelectionTips < 3 + if displayTextSelectionTip { + let _ = ApplicationSpecificNotice.incrementChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager).start() + tip = .textSelection + } } } @@ -1145,7 +1154,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) } else { - strongSelf.sendMessages(messages) + let transformedMessages = strongSelf.transformEnqueueMessages(messages) + strongSelf.sendMessages(transformedMessages) } return true }, sendGif: { [weak self] fileReference, sourceNode, sourceRect, silentPosting, schedule in @@ -1180,6 +1190,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) } else { + messages = strongSelf.transformEnqueueMessages(messages) strongSelf.sendMessages(messages) } } @@ -3838,7 +3849,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.inputActivityDisposable = (self.typingActivityPromise.get() |> deliverOnMainQueue).start(next: { [weak self] value in - if let strongSelf = self { + if let strongSelf = self, strongSelf.presentationInterfaceState.interfaceState.editMessage == nil && strongSelf.presentationInterfaceState.subject != .scheduledMessages && strongSelf.presentationInterfaceState.currentSendAsPeerId == nil { strongSelf.context.account.updateLocalInputActivity(peerId: activitySpace, activity: .typingText, isPresent: value) } }) @@ -3852,7 +3863,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } |> deliverOnMainQueue).start(next: { [weak self] value in - if let strongSelf = self { + if let strongSelf = self, strongSelf.presentationInterfaceState.interfaceState.editMessage == nil && strongSelf.presentationInterfaceState.subject != .scheduledMessages && strongSelf.presentationInterfaceState.currentSendAsPeerId == nil { if value { strongSelf.context.account.updateLocalInputActivity(peerId: activitySpace, activity: .typingText, isPresent: false) } @@ -3862,7 +3873,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.recordingActivityDisposable = (self.recordingActivityPromise.get() |> deliverOnMainQueue).start(next: { [weak self] value in - if let strongSelf = self { + if let strongSelf = self, strongSelf.presentationInterfaceState.interfaceState.editMessage == nil && strongSelf.presentationInterfaceState.subject != .scheduledMessages && strongSelf.presentationInterfaceState.currentSendAsPeerId == nil { strongSelf.acquiredRecordingActivityDisposable?.dispose() switch value { case .voice: @@ -4176,6 +4187,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.importStateDisposable?.dispose() self.nextChannelToReadDisposable?.dispose() self.inviteRequestsDisposable.dispose() + self.sendAsPeersDisposable?.dispose() } public func updatePresentationMode(_ mode: ChatControllerPresentationMode) { @@ -4565,7 +4577,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return [FoundPeer(peer: peer, subscribers: nil)] } - let _ = (combineLatest(queue: Queue.mainQueue(), currentAccountPeer, self.context.account.postbox.peerView(id: self.chatLocation.peerId), self.context.engine.peers.sendAsAvailablePeers(peerId: self.chatLocation.peerId))) + self.sendAsPeersDisposable = (combineLatest(queue: Queue.mainQueue(), currentAccountPeer, self.context.account.postbox.peerView(id: self.chatLocation.peerId), self.context.engine.peers.sendAsAvailablePeers(peerId: self.chatLocation.peerId))) .start(next: { [weak self] currentAccountPeer, peerView, peers in guard let strongSelf = self else { return @@ -5335,7 +5347,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } self.chatDisplayNode.updateTypingActivity = { [weak self] value in - if let strongSelf = self, strongSelf.presentationInterfaceState.interfaceState.editMessage == nil && strongSelf.presentationInterfaceState.subject != .scheduledMessages { + if let strongSelf = self { if value { strongSelf.typingActivityPromise.set(Signal.single(true) |> then( diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 309249b98b..077f4fde8f 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -436,7 +436,6 @@ private final class PeerInfoInteraction { let editingOpenInviteLinksSetup: () -> Void let editingOpenDiscussionGroupSetup: () -> Void let editingToggleMessageSignatures: (Bool) -> Void - let editingToggleChannelMessageCopyProtection: (Bool) -> Void let openParticipantsSection: (PeerInfoParticipantsSection) -> Void let editingOpenPreHistorySetup: () -> Void let editingOpenAutoremoveMesages: () -> Void @@ -477,7 +476,6 @@ private final class PeerInfoInteraction { editingOpenInviteLinksSetup: @escaping () -> Void, editingOpenDiscussionGroupSetup: @escaping () -> Void, editingToggleMessageSignatures: @escaping (Bool) -> Void, - editingToggleChannelMessageCopyProtection: @escaping (Bool) -> Void, openParticipantsSection: @escaping (PeerInfoParticipantsSection) -> Void, editingOpenPreHistorySetup: @escaping () -> Void, editingOpenAutoremoveMesages: @escaping () -> Void, @@ -517,7 +515,6 @@ private final class PeerInfoInteraction { self.editingOpenInviteLinksSetup = editingOpenInviteLinksSetup self.editingOpenDiscussionGroupSetup = editingOpenDiscussionGroupSetup self.editingToggleMessageSignatures = editingToggleMessageSignatures - self.editingToggleChannelMessageCopyProtection = editingToggleChannelMessageCopyProtection self.openParticipantsSection = openParticipantsSection self.editingOpenPreHistorySetup = editingOpenPreHistorySetup self.editingOpenAutoremoveMesages = editingOpenAutoremoveMesages @@ -1117,7 +1114,6 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr let ItemDiscussionGroup = 3 let ItemSignMessages = 4 let ItemSignMessagesHelp = 5 - let ItemCopyProtection = 6 if channel.flags.contains(.isCreator) { let linkText: String @@ -1166,7 +1162,6 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr if channel.flags.contains(.isCreator) || (channel.adminRights != nil && channel.hasPermission(.sendMessages)) { let messagesShouldHaveSignatures: Bool - let messagesCopyProtection = channel.flags.contains(.copyProtectionEnabled) switch channel.info { case let .broadcast(info): messagesShouldHaveSignatures = info.flags.contains(.messagesShouldHaveSignatures) @@ -1177,10 +1172,6 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr interaction.editingToggleMessageSignatures(value) })) items[.peerSettings]!.append(PeerInfoScreenCommentItem(id: ItemSignMessagesHelp, text: presentationData.strings.Channel_SignMessages_Help)) - - items[.peerAdditionalSettings]!.append(PeerInfoScreenSwitchItem(id: ItemCopyProtection, text: "Restrict Saving Content", value: messagesCopyProtection, icon: UIImage(bundleImageName: "Chat/Info/GroupSignIcon"), toggled: { value in - interaction.editingToggleChannelMessageCopyProtection(value) - })) } case .group: let ItemUsername = 101 @@ -1597,9 +1588,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate editingToggleMessageSignatures: { [weak self] value in self?.editingToggleMessageSignatures(value: value) }, - editingToggleChannelMessageCopyProtection: { [weak self] value in - self?.editingToggleMessageCopyProtection(value: value) - }, openParticipantsSection: { [weak self] section in self?.openParticipantsSection(section: section) }, @@ -4708,11 +4696,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate private func editingToggleMessageSignatures(value: Bool) { self.toggleShouldChannelMessagesSignaturesDisposable.set(self.context.engine.peers.toggleShouldChannelMessagesSignatures(peerId: self.peerId, enabled: value).start()) } - - private func editingToggleMessageCopyProtection(value: Bool) { - self.toggleMessageCopyProtectionDisposable.set(self.context.engine.peers.toggleMessageCopyProtection(peerId: self.peerId, enabled: value).start()) - } - + private func openParticipantsSection(section: PeerInfoParticipantsSection) { guard let data = self.data, let peer = data.peer else { return