diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 651e5b6d1f..4ba6792a0b 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -1265,7 +1265,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { }, addDoNotTranslateLanguage: { _ in }, hideTranslationPanel: { }, openPremiumGift: { - }, openSuggestPost: { _ in + }, openSuggestPost: { _, _ in }, openPremiumRequiredForMessaging: { }, openStarsPurchase: { _ in }, openMessagePayment: { diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift index 84a3dfb9c1..bc7dbc2d85 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift @@ -62,6 +62,13 @@ public enum ChatTranslationDisplayType { } public final class ChatPanelInterfaceInteraction { + public enum OpenSuggestPostMode { + case `default` + case editMessage + case editTime + case editPrice + } + public let setupReplyMessage: (MessageId?, @escaping (ContainedViewLayoutTransition, @escaping () -> Void) -> Void) -> Void public let setupEditMessage: (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void public let beginMessageSelection: ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void @@ -168,7 +175,7 @@ public final class ChatPanelInterfaceInteraction { public let addDoNotTranslateLanguage: (String) -> Void public let hideTranslationPanel: () -> Void public let openPremiumGift: () -> Void - public let openSuggestPost: (Message?) -> Void + public let openSuggestPost: (Message?, OpenSuggestPostMode) -> Void public let openPremiumRequiredForMessaging: () -> Void public let openStarsPurchase: (Int64?) -> Void public let openMessagePayment: () -> Void @@ -291,7 +298,7 @@ public final class ChatPanelInterfaceInteraction { addDoNotTranslateLanguage: @escaping (String) -> Void, hideTranslationPanel: @escaping () -> Void, openPremiumGift: @escaping () -> Void, - openSuggestPost: @escaping (Message?) -> Void, + openSuggestPost: @escaping (Message?, OpenSuggestPostMode) -> Void, openPremiumRequiredForMessaging: @escaping () -> Void, openStarsPurchase: @escaping (Int64?) -> Void, openMessagePayment: @escaping () -> Void, @@ -544,7 +551,7 @@ public final class ChatPanelInterfaceInteraction { }, addDoNotTranslateLanguage: { _ in }, hideTranslationPanel: { }, openPremiumGift: { - }, openSuggestPost: { _ in + }, openSuggestPost: { _, _ in }, openPremiumRequiredForMessaging: { }, openStarsPurchase: { _ in }, openMessagePayment: { diff --git a/submodules/TelegramCore/Sources/State/PaidMessages.swift b/submodules/TelegramCore/Sources/State/PaidMessages.swift index 226710fec0..035dcf363c 100644 --- a/submodules/TelegramCore/Sources/State/PaidMessages.swift +++ b/submodules/TelegramCore/Sources/State/PaidMessages.swift @@ -107,6 +107,7 @@ func _internal_reinstateNoPaidMessagesException(account: Account, scopePeerId: P } var flags: Int32 = 0 flags |= (1 << 2) + flags |= (1 << 1) return account.network.request(Api.functions.account.toggleNoPaidMessagesException(flags: flags, parentPeer: scopeInputPeer, userId: inputUser)) |> `catch` { _ -> Signal in return .single(.boolFalse) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/BUILD index 4d00881529..b5507029f4 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/BUILD @@ -53,6 +53,7 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode", "//submodules/TelegramUI/Components/Chat/MessageHaptics", "//submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift index 67834c78ce..8fd93f0bcf 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -46,6 +46,7 @@ import ChatMessageReactionsFooterContentNode import ManagedDiceAnimationNode import MessageHaptics import ChatMessageTransitionNode +import ChatMessageSuggestedPostInfoNode private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) @@ -143,6 +144,8 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { private var fetchEffectDisposable: Disposable? + private var suggestedPostInfoNode: ChatMessageSuggestedPostInfoNode? + required public init(rotated: Bool) { self.contextSourceNode = ContextExtractedContentContainingNode() self.containerNode = ContextControllerSourceNode() @@ -823,6 +826,8 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let textLayout = TextNodeWithEntities.asyncLayout(self.textNode) + let makeSuggestedPostInfoNodeLayout: ChatMessageSuggestedPostInfoNode.AsyncLayout = ChatMessageSuggestedPostInfoNode.asyncLayout(self.suggestedPostInfoNode) + func continueAsyncLayout(_ weakSelf: Weak, _ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: ChatMessageHeaderSpec) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) { let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: nil) let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData) @@ -1030,7 +1035,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var innerImageSize = imageSize imageSize = CGSize(width: imageSize.width + imageInset * 2.0, height: imageSize.height + imageInset * 2.0) - let imageFrame = CGRect(origin: CGPoint(x: 0.0 + (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - imageSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset - imageHorizontalOffset)), y: imageVerticalInset + imageTopPadding), size: CGSize(width: imageSize.width, height: imageSize.height)) + var imageFrame = CGRect(origin: CGPoint(x: 0.0 + (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - imageSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset - imageHorizontalOffset)), y: imageVerticalInset + imageTopPadding), size: CGSize(width: imageSize.width, height: imageSize.height)) if isEmoji { innerImageSize = imageSize } @@ -1281,10 +1286,48 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { needsForwardBackground = true } + let baseWidth = params.width - params.leftInset - params.rightInset + var maxContentWidth = imageSize.width var actionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode))? if let replyMarkup = replyMarkup { - let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, [:], item.message, maxContentWidth) + let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, [:], item.message, baseWidth) + maxContentWidth = max(maxContentWidth, minWidth) + actionButtonsFinalize = buttonsLayout + } else if incoming, let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil { + //TODO:localize + var buttonDeclineValue: UInt8 = 0 + let buttonDecline = MemoryBuffer(data: Data(bytes: &buttonDeclineValue, count: 1)) + var buttonApproveValue: UInt8 = 1 + let buttonApprove = MemoryBuffer(data: Data(bytes: &buttonApproveValue, count: 1)) + var buttonSuggestChangesValue: UInt8 = 2 + let buttonSuggestChanges = MemoryBuffer(data: Data(bytes: &buttonSuggestChangesValue, count: 1)) + + let customIcons: [MemoryBuffer: ChatMessageActionButtonsNode.CustomIcon] = [ + buttonDecline: .suggestedPostReject, + buttonApprove: .suggestedPostApprove, + buttonSuggestChanges: .suggestedPostEdit + ] + + let (minWidth, buttonsLayout) = actionButtonsLayout( + item.context, + item.presentationData.theme, + item.presentationData.chatBubbleCorners, + item.presentationData.strings, + item.controllerInteraction.presentationContext.backgroundNode, + ReplyMarkupMessageAttribute( + rows: [ + ReplyMarkupRow(buttons: [ + ReplyMarkupButton(title: "Decline", titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline)), + ReplyMarkupButton(title: "Approve", titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove)) + ]), + ReplyMarkupRow(buttons: [ + ReplyMarkupButton(title: "Suggest Changes", titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonSuggestChanges)) + ]) + ], + flags: [], + placeholder: nil + ), customIcons, item.message, baseWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } @@ -1336,6 +1379,21 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { layoutSize.height += 4.0 + reactionButtonsSizeAndApply.0.height } + var suggestedPostInfoNodeLayout: (CGSize, () -> ChatMessageSuggestedPostInfoNode)? + for attribute in item.message.attributes { + if let _ = attribute as? SuggestedPostMessageAttribute { + let suggestedPostInfoNodeLayoutValue = makeSuggestedPostInfoNodeLayout(item, baseWidth) + suggestedPostInfoNodeLayout = suggestedPostInfoNodeLayoutValue + } + } + + var additionalTopHeight: CGFloat = 0.0 + if let suggestedPostInfoNodeLayout { + additionalTopHeight += 4.0 + suggestedPostInfoNodeLayout.0.height + 8.0 + } + layoutSize.height += additionalTopHeight + imageFrame.origin.y += additionalTopHeight + var headersOffset: CGFloat = 0.0 if let (threadInfoSize, _) = threadInfoApply { headersOffset += threadInfoSize.height + 10.0 @@ -1390,6 +1448,20 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { strongSelf.updateAttachedDateHeader(hasDate: dateHeaderAtBottom.hasDate, hasPeer: dateHeaderAtBottom.hasTopic) + if let (suggestedPostInfoSize, suggestedPostInfoApply) = suggestedPostInfoNodeLayout { + let suggestedPostInfoNode = suggestedPostInfoApply() + if suggestedPostInfoNode !== strongSelf.suggestedPostInfoNode { + strongSelf.suggestedPostInfoNode?.removeFromSupernode() + strongSelf.suggestedPostInfoNode = suggestedPostInfoNode + strongSelf.addSubnode(suggestedPostInfoNode) + } + let suggestedPostInfoFrame = CGRect(origin: CGPoint(x: floor((params.width - suggestedPostInfoSize.width) * 0.5), y: 4.0), size: suggestedPostInfoSize) + suggestedPostInfoNode.frame = suggestedPostInfoFrame + } else if let suggestedPostInfoNode = strongSelf.suggestedPostInfoNode { + strongSelf.suggestedPostInfoNode = nil + suggestedPostInfoNode.removeFromSupernode() + } + strongSelf.messageAccessibilityArea.frame = CGRect(origin: CGPoint(), size: layoutSize) strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize) strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layoutSize) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index cf5fa73d75..cfd5d507aa 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -2805,7 +2805,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI actionButtonsFinalize = buttonsLayout lastNodeTopPosition = .None(.Both) - } else if incoming, /*let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = item.message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect),*/ let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil { + } else if incoming, let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil { //TODO:localize var buttonDeclineValue: UInt8 = 0 let buttonDecline = MemoryBuffer(data: Data(bytes: &buttonDeclineValue, count: 1)) @@ -2838,7 +2838,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI ], flags: [], placeholder: nil - ), customIcons, item.message, maximumNodeWidth) + ), customIcons, item.message, baseWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout @@ -3542,7 +3542,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if suggestedPostInfoNode !== strongSelf.suggestedPostInfoNode { strongSelf.suggestedPostInfoNode?.removeFromSupernode() strongSelf.suggestedPostInfoNode = suggestedPostInfoNode - strongSelf.mainContextSourceNode.contentNode.addSubnode(suggestedPostInfoNode) + strongSelf.addSubnode(suggestedPostInfoNode) } let suggestedPostInfoFrame = CGRect(origin: CGPoint(x: floor((params.width - suggestedPostInfoSize.width) * 0.5), y: 4.0), size: suggestedPostInfoSize) suggestedPostInfoNode.frame = suggestedPostInfoFrame diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift index 1b79cf3821..bbdb19ca9a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift @@ -291,7 +291,7 @@ public func canAddMessageReactions(message: Message) -> Bool { return true } else { switch action.action { - case .unknown, .groupCreated, .channelMigratedFromGroup, .groupMigratedToChannel, .historyCleared, .customText, .botDomainAccessGranted, .botAppAccessGranted, .botSentSecureValues, .phoneNumberRequest, .webViewData, .topicCreated, .attachMenuBotAllowed, .requestedPeer, .giveawayLaunched: + case .unknown, .groupCreated, .channelMigratedFromGroup, .groupMigratedToChannel, .historyCleared, .customText, .botDomainAccessGranted, .botAppAccessGranted, .botSentSecureValues, .phoneNumberRequest, .webViewData, .topicCreated, .attachMenuBotAllowed, .requestedPeer, .giveawayLaunched, .suggestedPostApprovalStatus: return false default: return true diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/BUILD index 54233cea21..93400be766 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/BUILD @@ -38,6 +38,7 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode", "//submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode", "//submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer", + "//submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index 1388eb443a..ad8c362519 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -28,6 +28,7 @@ import ChatMessageThreadInfoNode import ChatMessageActionButtonsNode import ChatMessageReactionsFooterContentNode import ChatSwipeToReplyRecognizer +import ChatMessageSuggestedPostInfoNode private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) @@ -51,6 +52,8 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { public var telegramFile: TelegramMediaFile? private let fetchDisposable = MetaDisposable() + private var suggestedPostInfoNode: ChatMessageSuggestedPostInfoNode? + private var viaBotNode: TextNode? private let dateAndStatusNode: ChatMessageDateAndStatusNode private var threadInfoNode: ChatMessageThreadInfoNode? @@ -435,6 +438,8 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { let currentShareButtonNode = self.shareButtonNode let currentForwardInfo = self.appliedForwardInfo + let makeSuggestedPostInfoNodeLayout: ChatMessageSuggestedPostInfoNode.AsyncLayout = ChatMessageSuggestedPostInfoNode.asyncLayout(self.suggestedPostInfoNode) + func continueAsyncLayout(_ weakSelf: Weak, _ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: ChatMessageHeaderSpec) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) { let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: nil) @@ -591,7 +596,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { let innerImageInset: CGFloat = 10.0 let innerImageSize = CGSize(width: imageSize.width + innerImageInset * 2.0, height: imageSize.height + innerImageInset * 2.0) - let imageFrame = CGRect(origin: CGPoint(x: 0.0 + (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - innerImageSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: -innerImageInset), size: innerImageSize) + var imageFrame = CGRect(origin: CGPoint(x: 0.0 + (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - innerImageSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: -innerImageInset), size: innerImageSize) let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(top: innerImageInset, left: innerImageInset, bottom: innerImageInset, right: innerImageInset)) @@ -842,10 +847,48 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { needsForwardBackground = true } + let baseWidth = params.width - params.leftInset - params.rightInset + var maxContentWidth = imageSize.width var actionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode))? if let replyMarkup = replyMarkup { - let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, [:], item.message, maxContentWidth) + let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, [:], item.message, baseWidth) + maxContentWidth = max(maxContentWidth, minWidth) + actionButtonsFinalize = buttonsLayout + } else if incoming, let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil { + //TODO:localize + var buttonDeclineValue: UInt8 = 0 + let buttonDecline = MemoryBuffer(data: Data(bytes: &buttonDeclineValue, count: 1)) + var buttonApproveValue: UInt8 = 1 + let buttonApprove = MemoryBuffer(data: Data(bytes: &buttonApproveValue, count: 1)) + var buttonSuggestChangesValue: UInt8 = 2 + let buttonSuggestChanges = MemoryBuffer(data: Data(bytes: &buttonSuggestChangesValue, count: 1)) + + let customIcons: [MemoryBuffer: ChatMessageActionButtonsNode.CustomIcon] = [ + buttonDecline: .suggestedPostReject, + buttonApprove: .suggestedPostApprove, + buttonSuggestChanges: .suggestedPostEdit + ] + + let (minWidth, buttonsLayout) = actionButtonsLayout( + item.context, + item.presentationData.theme, + item.presentationData.chatBubbleCorners, + item.presentationData.strings, + item.controllerInteraction.presentationContext.backgroundNode, + ReplyMarkupMessageAttribute( + rows: [ + ReplyMarkupRow(buttons: [ + ReplyMarkupButton(title: "Decline", titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonDecline)), + ReplyMarkupButton(title: "Approve", titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonApprove)) + ]), + ReplyMarkupRow(buttons: [ + ReplyMarkupButton(title: "Suggest Changes", titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonSuggestChanges)) + ]) + ], + flags: [], + placeholder: nil + ), customIcons, item.message, baseWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } @@ -901,6 +944,21 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { layoutSize.height += actionButtonsSizeAndApply.0.height } + var suggestedPostInfoNodeLayout: (CGSize, () -> ChatMessageSuggestedPostInfoNode)? + for attribute in item.message.attributes { + if let _ = attribute as? SuggestedPostMessageAttribute { + let suggestedPostInfoNodeLayoutValue = makeSuggestedPostInfoNodeLayout(item, baseWidth) + suggestedPostInfoNodeLayout = suggestedPostInfoNodeLayoutValue + } + } + + var additionalTopHeight: CGFloat = 0.0 + if let suggestedPostInfoNodeLayout { + additionalTopHeight += 4.0 + suggestedPostInfoNodeLayout.0.height + 8.0 + } + layoutSize.height += additionalTopHeight + imageFrame.origin.y += additionalTopHeight + var updatedImageFrame = imageFrame.offsetBy(dx: 0.0, dy: floor((contentHeight - imageSize.height) / 2.0)) var dateOffset = CGPoint(x: dateAndStatusSize.width + 4.0, y: dateAndStatusSize.height + 16.0) @@ -1002,6 +1060,20 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { animation.animator.updateFrame(layer: strongSelf.placeholderNode.layer, frame: placeholderFrame, completion: nil) } + if let (suggestedPostInfoSize, suggestedPostInfoApply) = suggestedPostInfoNodeLayout { + let suggestedPostInfoNode = suggestedPostInfoApply() + if suggestedPostInfoNode !== strongSelf.suggestedPostInfoNode { + strongSelf.suggestedPostInfoNode?.removeFromSupernode() + strongSelf.suggestedPostInfoNode = suggestedPostInfoNode + strongSelf.addSubnode(suggestedPostInfoNode) + } + let suggestedPostInfoFrame = CGRect(origin: CGPoint(x: floor((params.width - suggestedPostInfoSize.width) * 0.5), y: 4.0), size: suggestedPostInfoSize) + suggestedPostInfoNode.frame = suggestedPostInfoFrame + } else if let suggestedPostInfoNode = strongSelf.suggestedPostInfoNode { + strongSelf.suggestedPostInfoNode = nil + suggestedPostInfoNode.removeFromSupernode() + } + strongSelf.messageAccessibilityArea.frame = CGRect(origin: CGPoint(), size: layoutSize) strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize) strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layoutSize) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift index 1dc6273fdd..8c9f2407a3 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift @@ -93,15 +93,82 @@ public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode { //TODO:localize let titleText: String - if !item.message.effectivelyIncoming(item.context.account.peerId) { - if item.message.attributes.contains(where: { $0 is ReplyMessageAttribute }) { - titleText = "You suggest a new price,\ntime and text for this message." + if let attribute = item.message.attributes.first(where: { $0 is ReplyMessageAttribute }) as? ReplyMessageAttribute { + var changedText = false + var changedMedia = false + var changedTime = false + var changedPrice = false + if let previousMessage = item.message.associatedMessages[attribute.messageId] { + if previousMessage.text != item.message.text { + changedText = true + } + let filteredMediaIds = item.message.media.compactMap { media -> EngineMedia.Id? in + if media is TelegramMediaImage || media is TelegramMediaFile { + return media.id + } else { + return nil + } + } + let filteredPreviousMediaIds = previousMessage.media.compactMap { media -> EngineMedia.Id? in + if media is TelegramMediaImage || media is TelegramMediaFile { + return media.id + } else { + return nil + } + } + if Set(filteredPreviousMediaIds) != Set(filteredMediaIds) { + changedMedia = true + } + if let previousAttribute = previousMessage.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute { + if previousAttribute.amount != amount { + changedPrice = true + } + if previousAttribute.timestamp != timestamp { + changedTime = true + } + } + } + + if !item.message.effectivelyIncoming(item.context.account.peerId) { + if changedText && changedMedia && changedPrice && changedTime { + titleText = "You suggest a new price,\ntime and contents for this message." + } else if changedText && changedPrice && changedTime { + titleText = "You suggest a new price,\ntime and text for this message." + } else if changedMedia && changedPrice && changedTime { + titleText = "You suggest a new price,\ntime and attachments for this message." + } else if changedPrice && changedTime { + titleText = "You suggest a new price and time\nfor this message." + } else if changedPrice { + titleText = "You suggest a new price\nfor this message." + } else if changedTime { + titleText = "You suggest a new time\nfor this message." + } else { + titleText = "You suggest changes\nfor this message." + } } else { - titleText = "You suggest to post\nthis message." + var channelName = "" + if item.message.author is TelegramChannel { + channelName = item.message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? " " + } + if changedText && changedMedia && changedPrice && changedTime { + titleText = "**\(channelName)** suggests a new price,\ntime and contents for this message." + } else if changedText && changedPrice && changedTime { + titleText = "**\(channelName)** suggests a new price,\ntime and text for this message." + } else if changedMedia && changedPrice && changedTime { + titleText = "**\(channelName)** suggests a new price,\ntime and attachments for this message." + } else if changedPrice && changedTime { + titleText = "**\(channelName)** suggests a new price and time\nfor this message." + } else if changedPrice { + titleText = "**\(channelName)** suggests a new price\nfor this message." + } else if changedTime { + titleText = "**\(channelName)** suggests a new time\nfor this message." + } else { + titleText = "**\(channelName)** suggests changes\nfor this message." + } } } else { - if item.message.author is TelegramChannel { - titleText = "**\(item.message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? " ")** suggests a new price,\ntime, and text for your message." + if !item.message.effectivelyIncoming(item.context.account.peerId) { + titleText = "You suggest to post\nthis message." } else { titleText = "**\(item.message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? " ")** suggests to post\nthis message." } diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift index c3c3e8bb96..1ccce6c161 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift @@ -165,7 +165,7 @@ public final class ChatRecentActionsController: TelegramBaseController { }, addDoNotTranslateLanguage: { _ in }, hideTranslationPanel: { }, openPremiumGift: { - }, openSuggestPost: { _ in + }, openSuggestPost: { _, _ in }, openPremiumRequiredForMessaging: { }, openStarsPurchase: { _ in }, openMessagePayment: { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index cce46fcecd..8ede849f48 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -430,7 +430,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, addDoNotTranslateLanguage: { _ in }, hideTranslationPanel: { }, openPremiumGift: { - }, openSuggestPost: { _ in + }, openSuggestPost: { _, _ in }, openPremiumRequiredForMessaging: { }, openStarsPurchase: { _ in }, openMessagePayment: { diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index 190bb9afc2..360adb14eb 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -818,7 +818,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { }, addDoNotTranslateLanguage: { _ in }, hideTranslationPanel: { }, openPremiumGift: { - }, openSuggestPost: { _ in + }, openSuggestPost: { _, _ in }, openPremiumRequiredForMessaging: { }, openStarsPurchase: { _ in }, openMessagePayment: { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index bf51e55999..5109478202 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -4141,7 +4141,7 @@ extension ChatControllerImpl { }) self.push(controller) } - }, openSuggestPost: { [weak self] message in + }, openSuggestPost: { [weak self] message, mode in guard let self else { return } @@ -4198,6 +4198,13 @@ extension ChatControllerImpl { } return updated }) + + switch mode { + case .default, .editMessage: + break + case .editTime, .editPrice: + self.presentSuggestPostOptions() + } } else { self.updateChatPresentationInterfaceState(interactive: true, { state in var state = state @@ -4212,8 +4219,8 @@ extension ChatControllerImpl { } return state }) + self.presentSuggestPostOptions() } - self.presentSuggestPostOptions() }, openPremiumRequiredForMessaging: { [weak self] in guard let self else { return diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index e28478e01a..e8e6659451 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -2361,10 +2361,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G timestamp = Int32(Date().timeIntervalSince1970) + 1 * 60 * 60 } else { - let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: timestamp)).startStandalone() + //TODO:localize + let textString = "Publish this message now?" + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: textString, actions: [ + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), + TextAlertAction(type: .defaultAction, title: "Publish", action: { [weak strongSelf] in + guard let strongSelf else { + return + } + let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: timestamp)).startStandalone() + }) + ]), in: .window(.root)) } case 2: - strongSelf.interfaceInteraction?.openSuggestPost(message) + strongSelf.interfaceInteraction?.openSuggestPost(message, .default) default: break } diff --git a/submodules/TelegramUI/Sources/ChatControllerContentData.swift b/submodules/TelegramUI/Sources/ChatControllerContentData.swift index a6f8845dfe..c7c8fe80d9 100644 --- a/submodules/TelegramUI/Sources/ChatControllerContentData.swift +++ b/submodules/TelegramUI/Sources/ChatControllerContentData.swift @@ -819,13 +819,23 @@ extension ChatControllerImpl { if let channel = peerView.peers[peerView.peerId] as? TelegramChannel { if channel.isMonoForum { if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { + } else if let sendPaidMessageStarsValue = cachedData.sendPaidMessageStars, sendPaidMessageStarsValue == .zero { + sendPaidMessageStars = nil } else { sendPaidMessageStars = channel.sendPaidMessageStars } } else { if channel.flags.contains(.isCreator) || channel.adminRights != nil { } else { - sendPaidMessageStars = channel.sendPaidMessageStars + if let personalSendPaidMessageStars = cachedData.sendPaidMessageStars { + if personalSendPaidMessageStars == .zero { + sendPaidMessageStars = nil + } else { + sendPaidMessageStars = personalSendPaidMessageStars + } + } else { + sendPaidMessageStars = channel.sendPaidMessageStars + } } } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index a51b38f894..cd06f11f36 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -1512,14 +1512,47 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } if let message = messages.first, message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, channel.isMonoForum { - //TODO:localize - actions.append(.action(ContextMenuActionItem(text: "Suggest a Post", icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Customize"), color: theme.actionSheet.primaryTextColor) - }, action: { c, _ in - c?.dismiss(completion: { - interfaceInteraction.openSuggestPost(message) - }) - }))) + var canSuggestPost = true + for media in message.media { + if media is TelegramMediaAction { + canSuggestPost = false + } + } + + if canSuggestPost { + //TODO:localize + if message.attributes.contains(where: { $0 is SuggestedPostMessageAttribute }) { + actions.append(.action(ContextMenuActionItem(text: "Edit Message", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor) + }, action: { c, _ in + c?.dismiss(completion: { + interfaceInteraction.openSuggestPost(message, .editMessage) + }) + }))) + actions.append(.action(ContextMenuActionItem(text: "Edit Time", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Calendar"), color: theme.actionSheet.primaryTextColor) + }, action: { c, _ in + c?.dismiss(completion: { + interfaceInteraction.openSuggestPost(message, .editTime) + }) + }))) + actions.append(.action(ContextMenuActionItem(text: "Edit Price", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/PriceTag"), color: theme.actionSheet.primaryTextColor) + }, action: { c, _ in + c?.dismiss(completion: { + interfaceInteraction.openSuggestPost(message, .editPrice) + }) + }))) + } else { + actions.append(.action(ContextMenuActionItem(text: "Suggest a Post", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Customize"), color: theme.actionSheet.primaryTextColor) + }, action: { c, _ in + c?.dismiss(completion: { + interfaceInteraction.openSuggestPost(message, .default) + }) + }))) + } + } } if let activePoll = activePoll, let voters = activePoll.results.voters { @@ -1935,6 +1968,11 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let associatedPeerId = channel.associatedPeerId { if message.effectivelyIncoming(context.account.peerId), message.author?.id == associatedPeerId { canViewAuthor = true + for media in message.media { + if media is TelegramMediaAction { + canViewAuthor = false + } + } } } else if let messageReadStatsAreHidden = infoSummaryData.messageReadStatsAreHidden, !messageReadStatsAreHidden { canViewStats = canViewReadStats(message: message, participantCount: infoSummaryData.participantCount, isMessageRead: isMessageRead, isPremium: isPremium, appConfig: appConfig) diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 8c87be008e..d2af18fb7d 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -4773,7 +4773,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch case .gift: self.interfaceInteraction?.openPremiumGift() case .suggestPost: - self.interfaceInteraction?.openSuggestPost(nil) + self.interfaceInteraction?.openSuggestPost(nil, .default) } break }