diff --git a/submodules/Display/Source/Nodes/ASImageNode.swift b/submodules/Display/Source/Nodes/ASImageNode.swift index 183f0632e7..07135890f7 100644 --- a/submodules/Display/Source/Nodes/ASImageNode.swift +++ b/submodules/Display/Source/Nodes/ASImageNode.swift @@ -23,6 +23,12 @@ open class ASImageNode: ASDisplayNode { } } } + + public var customTintColor: UIColor? { + didSet { + self.layer.layerTintColor = self.customTintColor?.cgColor + } + } public var displayWithoutProcessing: Bool = true @@ -42,6 +48,7 @@ open class ASImageNode: ASDisplayNode { ASDisplayNodeSetResizableContents(self.layer, image) } } + self.layer.layerTintColor = self.customTintColor?.cgColor } override public func calculateSizeThatFits(_ contrainedSize: CGSize) -> CGSize { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 6303f27d11..b1e1a49b2d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -1591,7 +1591,6 @@ public extension TelegramEngine { public enum MonoforumSuggestedPostAction { case approve(timestamp: Int32?) case reject(comment: String?) - case proposeChanges(amount: Int64, timestamp: Int32?) } public func monoforumPerformSuggestedPostAction(id: EngineMessage.Id, action: MonoforumSuggestedPostAction) -> Signal { @@ -1602,34 +1601,6 @@ public extension TelegramEngine { func _internal_monoforumPerformSuggestedPostAction(account: Account, id: EngineMessage.Id, action: TelegramEngine.Messages.MonoforumSuggestedPostAction) -> Signal { return account.postbox.transaction { transaction -> Api.InputPeer? in - if case let .proposeChanges(amount, timestamp) = action, let message = transaction.getMessage(id) { - var attributes: [MessageAttribute] = [] - attributes.append(SuggestedPostMessageAttribute( - amount: amount, - timestamp: timestamp, - state: nil - )) - - var mediaReference: AnyMediaReference? - if let media = message.media.first { - mediaReference = .message(message: MessageReference(message), media: media) - } - - let enqueueMessage: EnqueueMessage = .message( - text: message.text, - attributes: attributes, - inlineStickers: [:], - mediaReference: mediaReference, - threadId: message.threadId, - replyToMessageId: EngineMessageReplySubject(messageId: message.id, quote: nil), - replyToStoryId: nil, - localGroupingKey: nil, - correlationId: nil, - bubbleUpEmojiOrStickersets: [] - ) - let _ = enqueueMessages(transaction: transaction, account: account, peerId: id.peerId, messages: [(true, enqueueMessage)]) - } - return transaction.getPeer(id.peerId).flatMap(apiInputPeer) } |> mapToSignal { inputPeer -> Signal in @@ -1655,8 +1626,6 @@ func _internal_monoforumPerformSuggestedPostAction(account: Account, id: EngineM if rejectComment != nil { flags |= 1 << 2 } - case .proposeChanges: - flags |= 1 << 1 } return account.network.request(Api.functions.messages.toggleSuggestedPostApproval(flags: flags, peer: inputPeer, msgId: id.id, scheduleDate: timestamp, rejectComment: rejectComment)) |> map(Optional.init) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 1abc3d3ac9..49b197b86f 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -346,6 +346,10 @@ public enum PresentationResourceKey: Int32 { case callListCallIcon case chatFreeNavigateToThreadButtonIcon + + case messageButtonsPostReject + case messageButtonsPostApprove + case messageButtonsPostEdit } public enum ChatExpiredStoryIndicatorType: Hashable { diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 76e5474a83..65f4204d5c 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -1432,4 +1432,22 @@ public struct PresentationResourcesChat { return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/TodoCheck"), color: theme.chat.message.outgoing.accentTextColor) }) } + + public static func messageButtonsPostReject(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.messageButtonsPostReject.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/SuggestPostDecline"), color: .white) + }) + } + + public static func messageButtonsPostApprove(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.messageButtonsPostApprove.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/SuggestPostApprove"), color: .white) + }) + } + + public static func messageButtonsPostEdit(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.messageButtonsPostEdit.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/SuggestPostChange"), color: .white) + }) + } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift index 18f785f044..82af123a30 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift @@ -161,16 +161,32 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { } } - class func asyncLayout(_ maybeNode: ChatMessageActionButtonNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ bubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ message: Message, _ button: ReplyMarkupButton, _ constrainedWidth: CGFloat, _ position: MessageBubbleActionButtonPosition) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode))) { + class func asyncLayout(_ maybeNode: ChatMessageActionButtonNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ bubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ message: Message, _ button: ReplyMarkupButton, _ customIcon: ChatMessageActionButtonsNode.CustomIcon?, _ constrainedWidth: CGFloat, _ position: MessageBubbleActionButtonPosition) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode))) { let titleLayout = TextNode.asyncLayout(maybeNode?.titleNode) - return { context, theme, bubbleCorners, strings, backgroundNode, message, button, constrainedWidth, position in + return { context, theme, bubbleCorners, strings, backgroundNode, message, button, customIcon, constrainedWidth, position in let incoming = message.effectivelyIncoming(context.account.peerId) let graphics = PresentationResourcesChat.additionalGraphics(theme.theme, wallpaper: theme.wallpaper, bubbleCorners: bubbleCorners) + let messageTheme = incoming ? theme.theme.chat.message.incoming : theme.theme.chat.message.outgoing + + let titleColor = bubbleVariableColor(variableColor: messageTheme.actionButtonsTextColor, wallpaper: theme.wallpaper) + var isStarsPayment = false let iconImage: UIImage? - switch button.action { + var tintColor: UIColor? + if let customIcon { + switch customIcon { + case .suggestedPostReject: + iconImage = PresentationResourcesChat.messageButtonsPostReject(theme.theme) + case .suggestedPostApprove: + iconImage = PresentationResourcesChat.messageButtonsPostApprove(theme.theme) + case .suggestedPostEdit: + iconImage = PresentationResourcesChat.messageButtonsPostEdit(theme.theme) + } + tintColor = titleColor + } else { + switch button.action { case .text: iconImage = incoming ? graphics.chatBubbleActionButtonIncomingMessageIconImage : graphics.chatBubbleActionButtonOutgoingMessageIconImage case let .url(value): @@ -214,6 +230,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { iconImage = incoming ? graphics.chatBubbleActionButtonIncomingCopyIconImage : graphics.chatBubbleActionButtonOutgoingCopyIconImage default: iconImage = nil + } } let sideInset: CGFloat = 8.0 @@ -230,9 +247,6 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { } } - let messageTheme = incoming ? theme.theme.chat.message.incoming : theme.theme.chat.message.outgoing - - let titleColor = bubbleVariableColor(variableColor: messageTheme.actionButtonsTextColor, wallpaper: theme.wallpaper) let attributedTitle: NSAttributedString if isStarsPayment { let updatedTitle = title.replacingOccurrences(of: "⭐️", with: " # ") @@ -247,9 +261,15 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { attributedTitle = NSAttributedString(string: title, font: titleFont, textColor: titleColor) } - let (titleSize, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: attributedTitle, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(44.0, constrainedWidth - minimumSideInset - minimumSideInset), height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0))) + var customIconSpaceWidth: CGFloat = 0.0 + if let iconImage, customIcon != nil { + customIconSpaceWidth = 3.0 + iconImage.size.width + } + + let (titleSize, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: attributedTitle, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(44.0, constrainedWidth - minimumSideInset - minimumSideInset - customIconSpaceWidth), height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0))) + let contentWidth = titleSize.size.width + sideInset + sideInset + customIconSpaceWidth - return (titleSize.size.width + sideInset + sideInset, { width in + return (contentWidth, { width in return (CGSize(width: width, height: 42.0), { animation in var animation = animation @@ -361,6 +381,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { node.addSubnode(iconNode) } node.iconNode?.image = iconImage + node.iconNode?.customTintColor = tintColor } else if node.iconNode != nil { node.iconNode?.removeFromSupernode() node.iconNode = nil @@ -373,7 +394,10 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { titleNode.isUserInteractionEnabled = false } - let titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.size.width) / 2.0), y: floor((42.0 - titleSize.size.height) / 2.0) + 1.0), size: titleSize.size) + var titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.size.width) / 2.0), y: floor((42.0 - titleSize.size.height) / 2.0) + 1.0), size: titleSize.size) + if let image = node.iconNode?.image, customIcon != nil { + titleFrame.origin.x = floorToScreenPixels((width - titleSize.size.width - image.size.width - 3.0) * 0.5) + 3.0 + image.size.width + } titleNode.layer.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) animation.animator.updatePosition(layer: titleNode.layer, position: CGPoint(x: titleFrame.midX, y: titleFrame.midY), completion: nil) @@ -381,7 +405,13 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { buttonView.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: 42.0)) } if let iconNode = node.iconNode { - animation.animator.updateFrame(layer: iconNode.layer, frame: CGRect(x: width - 16.0, y: 4.0, width: 12.0, height: 12.0), completion: nil) + let iconFrame: CGRect + if customIcon != nil, let image = iconNode.image { + iconFrame = CGRect(x: titleFrame.minX - 3.0 - image.size.width, y: titleFrame.minY + floorToScreenPixels((titleFrame.height - image.size.height) * 0.5) - 1.0, width: image.size.width, height: image.size.height) + } else { + iconFrame = CGRect(x: width - 16.0, y: 4.0, width: 12.0, height: 12.0) + } + animation.animator.updateFrame(layer: iconNode.layer, frame: iconFrame, completion: nil) } if let (rect, size) = node.absolutePosition { @@ -399,6 +429,12 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { } public final class ChatMessageActionButtonsNode: ASDisplayNode { + public enum CustomIcon { + case suggestedPostApprove + case suggestedPostReject + case suggestedPostEdit + } + private var buttonNodes: [ChatMessageActionButtonNode] = [] private var buttonPressedWrapper: ((ReplyMarkupButton) -> Void)? @@ -435,10 +471,10 @@ public final class ChatMessageActionButtonsNode: ASDisplayNode { } } - public class func asyncLayout(_ maybeNode: ChatMessageActionButtonsNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ chatBubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ replyMarkup: ReplyMarkupMessageAttribute, _ message: Message, _ constrainedWidth: CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)) { + public class func asyncLayout(_ maybeNode: ChatMessageActionButtonsNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ chatBubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ replyMarkup: ReplyMarkupMessageAttribute, _ customIcons: [MemoryBuffer: CustomIcon], _ message: Message, _ constrainedWidth: CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)) { let currentButtonLayouts = maybeNode?.buttonNodes.map { ChatMessageActionButtonNode.asyncLayout($0) } ?? [] - return { context, theme, chatBubbleCorners, strings, backgroundNode, replyMarkup, message, constrainedWidth in + return { context, theme, chatBubbleCorners, strings, backgroundNode, replyMarkup, customIcons, message, constrainedWidth in let buttonHeight: CGFloat = 42.0 let buttonSpacing: CGFloat = 2.0 @@ -454,6 +490,11 @@ public final class ChatMessageActionButtonsNode: ASDisplayNode { var finalizeRowButtonLayouts: [((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode))] = [] var rowButtonIndex = 0 for button in row.buttons { + var customIcon: CustomIcon? + if case let .callback(_, data) = button.action { + customIcon = customIcons[data] + } + let buttonPosition: MessageBubbleActionButtonPosition if rowIndex == replyMarkup.rows.count - 1 { if row.buttons.count == 1 { @@ -471,9 +512,9 @@ public final class ChatMessageActionButtonsNode: ASDisplayNode { let prepareButtonLayout: (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode))) if buttonIndex < currentButtonLayouts.count { - prepareButtonLayout = currentButtonLayouts[buttonIndex](context, theme, chatBubbleCorners, strings, backgroundNode, message, button, maximumButtonWidth, buttonPosition) + prepareButtonLayout = currentButtonLayouts[buttonIndex](context, theme, chatBubbleCorners, strings, backgroundNode, message, button, customIcon, maximumButtonWidth, buttonPosition) } else { - prepareButtonLayout = ChatMessageActionButtonNode.asyncLayout(nil)(context, theme, chatBubbleCorners, strings, backgroundNode, message, button, maximumButtonWidth, buttonPosition) + prepareButtonLayout = ChatMessageActionButtonNode.asyncLayout(nil)(context, theme, chatBubbleCorners, strings, backgroundNode, message, button, customIcon, maximumButtonWidth, buttonPosition) } maximumRowButtonWidth = max(maximumRowButtonWidth, prepareButtonLayout.minimumWidth) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift index 1019e22c1b..f73ab88e85 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -1284,7 +1284,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { 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, maxContentWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 93983169f4..06f98b2c84 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -1489,7 +1489,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI threadInfoLayout: (ChatMessageThreadInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode), forwardInfoLayout: (AccountContext, ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, ChatMessageForwardInfoNode.StoryData?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode), replyInfoLayout: (ChatMessageReplyInfoNode.Arguments) -> (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode), - actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)), + actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, [MemoryBuffer: ChatMessageActionButtonsNode.CustomIcon], Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)), reactionButtonsLayout: (ChatMessageReactionButtonsNode.Arguments) -> (minWidth: CGFloat, layout: (CGFloat) -> (size: CGSize, apply: (ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode)), unlockButtonLayout: (ChatMessageUnlockMediaNode.Arguments) -> (CGSize, (Bool) -> ChatMessageUnlockMediaNode), mediaInfoLayout: (ChatMessageStarsMediaInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageStarsMediaInfoNode), @@ -2798,16 +2798,25 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI ], flags: [], placeholder: nil - ), item.message, maximumNodeWidth) + ), [:], item.message, maximumNodeWidth) maxContentWidth = max(maxContentWidth, minWidth) 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 { //TODO:localize - var buttonDecline: UInt8 = 0 - var buttonApprove: UInt8 = 1 - var buttonSuggestChanges: UInt8 = 2 + 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, @@ -2818,22 +2827,22 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI ReplyMarkupMessageAttribute( rows: [ ReplyMarkupRow(buttons: [ - ReplyMarkupButton(title: "Decline", titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: MemoryBuffer(data: Data(bytes: &buttonDecline, count: 1)))), - ReplyMarkupButton(title: "Approve", titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: MemoryBuffer(data: Data(bytes: &buttonApprove, count: 1)))) + 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: MemoryBuffer(data: Data(bytes: &buttonSuggestChanges, count: 1)))) + ReplyMarkupButton(title: "Suggest Changes", titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: buttonSuggestChanges)) ]) ], flags: [], placeholder: nil - ), item.message, maximumNodeWidth) + ), customIcons, item.message, maximumNodeWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout lastNodeTopPosition = .None(.Both) } else if let replyMarkup = replyMarkup, !item.presentationData.isPreview { - let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, item.message, maximumNodeWidth) + let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, [:], item.message, maximumNodeWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift index 4a4932ecf2..bc4dca41fe 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift @@ -590,7 +590,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco var maxContentWidth = normalDisplaySize.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, maxContentWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index b18c87080f..d26834db75 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -845,7 +845,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { 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, maxContentWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } diff --git a/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/Sources/SuggestPostAccessoryPanelNode.swift b/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/Sources/SuggestPostAccessoryPanelNode.swift index f6159b5f94..906cea9034 100644 --- a/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/Sources/SuggestPostAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/Sources/SuggestPostAccessoryPanelNode.swift @@ -93,11 +93,6 @@ public final class SuggestPostAccessoryPanelNode: AccessoryPanelNode { self.addSubnode(self.titleNode) self.addSubnode(self.textNode) self.addSubnode(self.actionArea) - - //TODO:localize - var titleText: [CompositeTextNode.Component] = [] - titleText.append(.text(NSAttributedString(string: "Suggest a post below", font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor))) - self.titleNode.components = titleText } deinit { @@ -187,11 +182,6 @@ public final class SuggestPostAccessoryPanelNode: AccessoryPanelNode { let imageTextInset: CGFloat = 0.0 - let titleSize = self.titleNode.update(constrainedSize: CGSize(width: bounds.size.width - leftInset - textLineInset - rightInset - textRightInset - imageTextInset, height: bounds.size.height)) - if self.titleNode.supernode == self { - self.titleNode.frame = CGRect(origin: CGPoint(x: leftInset + textLineInset + imageTextInset, y: 7.0), size: titleSize) - } - let textFont = Font.regular(15.0) var inlineTextStarImage: UIImage? @@ -213,6 +203,20 @@ public final class SuggestPostAccessoryPanelNode: AccessoryPanelNode { } } + //TODO:localize + var titleText: [CompositeTextNode.Component] = [] + if let postSuggestionState = interfaceState.interfaceState.postSuggestionState, postSuggestionState.editingOriginalMessageId != nil { + titleText.append(.text(NSAttributedString(string: "Suggest Changes", font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor))) + } else { + titleText.append(.text(NSAttributedString(string: "Suggest a post below", font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor))) + } + self.titleNode.components = titleText + + let titleSize = self.titleNode.update(constrainedSize: CGSize(width: bounds.size.width - leftInset - textLineInset - rightInset - textRightInset - imageTextInset, height: bounds.size.height)) + if self.titleNode.supernode == self { + self.titleNode.frame = CGRect(origin: CGPoint(x: leftInset + textLineInset + imageTextInset, y: 7.0), size: titleSize) + } + let textString: NSAttributedString if let postSuggestionState = interfaceState.interfaceState.postSuggestionState, postSuggestionState.price != 0 { if let timestamp = postSuggestionState.timestamp { diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 158edaf5ea..9689a9daaf 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -52,6 +52,7 @@ private final class SheetContent: CombinedComponent { static var body: (CombinedComponentContext) -> CGSize { let closeButton = Child(Button.self) + let balance = Child(BalanceComponent.self) let title = Child(Text.self) let amountSection = Child(ListSectionComponent.self) let amountAdditionalLabel = Child(MultilineTextComponent.self) @@ -61,7 +62,7 @@ private final class SheetContent: CombinedComponent { let balanceValue = Child(MultilineTextComponent.self) let balanceIcon = Child(BundleIconComponent.self) - return { (context: CombinedComponentContext) -> CGSize in + let body: (CombinedComponentContext) -> CGSize = { (context: CombinedComponentContext) -> CGSize in let environment = context.environment[EnvironmentType.self] let component = context.component let state = context.state @@ -79,26 +80,64 @@ private final class SheetContent: CombinedComponent { let constrainedTitleWidth = context.availableSize.width - 16.0 * 2.0 - let closeImage: UIImage - if let (image, theme) = state.cachedCloseImage, theme === environment.theme { - closeImage = image + if case let .suggestedPost(mode, _, _, _) = component.mode { + switch mode { + case .sender: + let balance = balance.update( + component: BalanceComponent( + context: component.context, + theme: environment.theme, + strings: environment.strings, + balance: state.balance, + alignment: .right + ), + availableSize: CGSize(width: 200.0, height: 200.0), + transition: .immediate + ) + let balanceFrame = CGRect(origin: CGPoint(x: context.availableSize.width - balance.size.width - 15.0, y: floor((56.0 - balance.size.height) * 0.5)), size: balance.size) + context.add(balance + .position(balanceFrame.center) + ) + case .admin: + break + } + + let closeButton = closeButton.update( + component: Button( + content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.list.itemAccentColor)), + action: { + component.dismiss() + } + ).minSize(CGSize(width: 8.0, height: 44.0)), + availableSize: CGSize(width: 200.0, height: 100.0), + transition: .immediate + ) + let closeFrame = CGRect(origin: CGPoint(x: 16.0, y: floor((56.0 - closeButton.size.height) * 0.5)), size: closeButton.size) + context.add(closeButton + .position(closeFrame.center) + ) } else { - closeImage = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0x808084, alpha: 0.1), foregroundColor: theme.actionSheet.inputClearButtonColor)! - state.cachedCloseImage = (closeImage, theme) + let closeImage: UIImage + if let (image, theme) = state.cachedCloseImage, theme === environment.theme { + closeImage = image + } else { + closeImage = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0x808084, alpha: 0.1), foregroundColor: theme.actionSheet.inputClearButtonColor)! + state.cachedCloseImage = (closeImage, theme) + } + let closeButton = closeButton.update( + component: Button( + content: AnyComponent(Image(image: closeImage)), + action: { + component.dismiss() + } + ), + availableSize: CGSize(width: 30.0, height: 30.0), + transition: .immediate + ) + context.add(closeButton + .position(CGPoint(x: context.availableSize.width - closeButton.size.width, y: 28.0)) + ) } - let closeButton = closeButton.update( - component: Button( - content: AnyComponent(Image(image: closeImage)), - action: { - component.dismiss() - } - ), - availableSize: CGSize(width: 30.0, height: 30.0), - transition: .immediate - ) - context.add(closeButton - .position(CGPoint(x: context.availableSize.width - closeButton.size.width, y: 28.0)) - ) let titleString: String let amountTitle: String @@ -171,7 +210,7 @@ private final class SheetContent: CombinedComponent { amountTitle = "ENTER A PRICE IN STARS" amountPlaceholder = "Price" - minAmount = StarsAmount(value: 1, nanos: 0) + minAmount = StarsAmount(value: 0, nanos: 0) //TODO:release maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0) } @@ -510,7 +549,7 @@ private final class SheetContent: CombinedComponent { if let amount = state.amount { buttonString = "Set # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))" } else { - buttonString = "Set" + buttonString = "Set # Free" } case .admin: buttonString = "Update Terms" @@ -541,6 +580,8 @@ private final class SheetContent: CombinedComponent { if minValue <= 0 { isButtonEnabled = true } + } else if case .suggestedPost = context.component.mode { + isButtonEnabled = true } let button = button.update( @@ -558,7 +599,9 @@ private final class SheetContent: CombinedComponent { isEnabled: isButtonEnabled, displaysProgress: false, action: { [weak state] in - if let controller = controller() as? StarsWithdrawScreen, let state, let amount = state.amount { + if let controller = controller() as? StarsWithdrawScreen, let state { + let amount = state.amount ?? StarsAmount.zero + if let minAmount, amount < minAmount { controller.presentMinAmountTooltip(minAmount.value) } else { @@ -599,6 +642,8 @@ private final class SheetContent: CombinedComponent { return contentSize } + + return body } final class State: ComponentState { @@ -647,7 +692,21 @@ private final class SheetContent: CombinedComponent { super.init() - if case .reaction = self.mode, let starsContext = context.starsContext { + var needsBalance = false + switch self.mode { + case .reaction: + needsBalance = true + case let .suggestedPost(mode, _, _, _): + switch mode { + case .sender: + needsBalance = true + case .admin: + break + } + default: + break + } + if needsBalance, let starsContext = component.context.starsContext { self.stateDisposable = (starsContext.state |> deliverOnMainQueue).startStrict(next: { [weak self] state in if let self, let balance = state?.balance { @@ -1064,8 +1123,8 @@ private final class AmountFieldComponent: Component { let size = CGSize(width: availableSize.width, height: 44.0) - let sideInset: CGFloat = 15.0 - var leftInset: CGFloat = 15.0 + let sideInset: CGFloat = 16.0 + var leftInset: CGFloat = 16.0 if let icon = self.iconView.image { leftInset += icon.size.width + 6.0 self.iconView.frame = CGRect(origin: CGPoint(x: 15.0, y: floorToScreenPixels((size.height - icon.size.height) / 2.0)), size: icon.size) @@ -1191,3 +1250,133 @@ private struct StarsWithdrawConfiguration { } } } + +private final class BalanceComponent: CombinedComponent { + let context: AccountContext + let theme: PresentationTheme + let strings: PresentationStrings + let balance: StarsAmount? + let alignment: NSTextAlignment + + init( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + balance: StarsAmount?, + alignment: NSTextAlignment + ) { + self.context = context + self.theme = theme + self.strings = strings + self.balance = balance + self.alignment = alignment + } + + static func ==(lhs: BalanceComponent, rhs: BalanceComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.balance != rhs.balance { + return false + } + if lhs.alignment != rhs.alignment { + return false + } + return true + } + + static var body: Body { + let title = Child(MultilineTextComponent.self) + let balance = Child(MultilineTextComponent.self) + let icon = Child(BundleIconComponent.self) + + return { context in + var size = CGSize(width: 0.0, height: 0.0) + + let title = title.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString(string: context.component.strings.SendStarReactions_Balance, font: Font.regular(14.0), textColor: context.component.theme.list.itemPrimaryTextColor)) + ), + availableSize: context.availableSize, + transition: .immediate + ) + + size.width = max(size.width, title.size.width) + size.height += title.size.height + + let balanceText: String + if let value = context.component.balance { + balanceText = "\(value.stringValue)" + } else { + balanceText = "..." + } + let balance = balance.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString(string: balanceText, font: Font.medium(15.0), textColor: context.component.theme.list.itemPrimaryTextColor)) + ), + availableSize: context.availableSize, + transition: .immediate + ) + + let iconSize = CGSize(width: 18.0, height: 18.0) + let icon = icon.update( + component: BundleIconComponent( + name: "Premium/Stars/StarLarge", + tintColor: nil + ), + availableSize: iconSize, + transition: context.transition + ) + + let titleSpacing: CGFloat = 1.0 + let iconSpacing: CGFloat = 2.0 + + size.height += titleSpacing + + size.width = max(size.width, icon.size.width + iconSpacing + balance.size.width) + size.height += balance.size.height + + if context.component.alignment == .right { + context.add( + title.position( + title.size.centered(in: CGRect(origin: CGPoint(x: size.width - title.size.width, y: 0.0), size: title.size)).center + ) + ) + context.add( + balance.position( + balance.size.centered(in: CGRect(origin: CGPoint(x: size.width - balance.size.width, y: title.size.height + titleSpacing), size: balance.size)).center + ) + ) + context.add( + icon.position( + icon.size.centered(in: CGRect(origin: CGPoint(x: size.width - balance.size.width - icon.size.width - 1.0, y: title.size.height + titleSpacing), size: icon.size)).center + ) + ) + } else { + context.add( + title.position( + title.size.centered(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: title.size)).center + ) + ) + context.add( + balance.position( + balance.size.centered(in: CGRect(origin: CGPoint(x: icon.size.width + iconSpacing, y: title.size.height + titleSpacing), size: balance.size)).center + ) + ) + context.add( + icon.position( + icon.size.centered(in: CGRect(origin: CGPoint(x: -1.0, y: title.size.height + titleSpacing), size: icon.size)).center + ) + ) + } + + return size + } + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/SuggestPostApprove.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/SuggestPostApprove.imageset/Contents.json new file mode 100644 index 0000000000..77ee1a7b05 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/SuggestPostApprove.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ok.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/SuggestPostApprove.imageset/ok.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/SuggestPostApprove.imageset/ok.pdf new file mode 100644 index 0000000000..af6c60104a Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Message/SuggestPostApprove.imageset/ok.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/SuggestPostChange.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/SuggestPostChange.imageset/Contents.json new file mode 100644 index 0000000000..d39fed8db6 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/SuggestPostChange.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "pencil.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/SuggestPostChange.imageset/pencil.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/SuggestPostChange.imageset/pencil.pdf new file mode 100644 index 0000000000..bb0ece0653 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Message/SuggestPostChange.imageset/pencil.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/SuggestPostDecline.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/SuggestPostDecline.imageset/Contents.json new file mode 100644 index 0000000000..0fb443a00f --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/SuggestPostDecline.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "cancel.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/SuggestPostDecline.imageset/cancel.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/SuggestPostDecline.imageset/cancel.pdf new file mode 100644 index 0000000000..aafd4077a4 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Message/SuggestPostDecline.imageset/cancel.pdf differ diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index a7ea404462..aa70977665 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -4464,11 +4464,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } func sendCurrentMessage(silentPosting: Bool? = nil, scheduleTime: Int32? = nil, postpone: Bool = false, messageEffect: ChatSendMessageEffect? = nil, completion: @escaping () -> Void = {}) { - if let postSuggestionState = self.chatPresentationInterfaceState.interfaceState.postSuggestionState, postSuggestionState.price == 0 { - self.interfaceInteraction?.presentSuggestPostOptions() - return - } - if let textInputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode { self.historyNode.justSentTextMessage = true