From f1b98f6dd8b7a0d924aa1eb3f441590a8bfa0c49 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 13 Jun 2025 17:54:04 +0800 Subject: [PATCH] Update API [skip ci] --- submodules/TelegramApi/Sources/Api0.swift | 2 +- submodules/TelegramApi/Sources/Api15.swift | 26 ++- submodules/TelegramApi/Sources/Api38.swift | 7 +- .../ApiUtils/TelegramMediaAction.swift | 6 +- .../SyncCore_TelegramMediaAction.swift | 30 ++- .../Messages/TelegramEngineMessages.swift | 30 ++- .../ChatMessageActionBubbleContentNode/BUILD | 1 + .../ChatMessageActionBubbleContentNode.swift | 216 +++++++++++++++++- .../Chat/ChatMessageBubbleItemNode/BUILD | 1 + .../Sources/ChatMessageBubbleItemNode.swift | 39 +++- .../ChatMessageSuggestedPostInfoNode/BUILD | 29 +++ .../ChatMessageSuggestedPostInfoNode.swift | 171 ++++++++++++++ .../TelegramUI/Sources/ChatController.swift | 4 +- 13 files changed, 519 insertions(+), 43 deletions(-) create mode 100644 submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/BUILD create mode 100644 submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 1f35056ff4..7d8a00d59a 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -607,7 +607,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1192749220] = { return Api.MessageAction.parse_messageActionStarGift($0) } dict[775611918] = { return Api.MessageAction.parse_messageActionStarGiftUnique($0) } dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) } - dict[866770590] = { return Api.MessageAction.parse_messageActionSuggestedPostApproval($0) } + dict[-1354584535] = { return Api.MessageAction.parse_messageActionSuggestedPostApproval($0) } dict[-940721021] = { return Api.MessageAction.parse_messageActionTodoAppendTasks($0) } dict[-864265079] = { return Api.MessageAction.parse_messageActionTodoCompletions($0) } dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) } diff --git a/submodules/TelegramApi/Sources/Api15.swift b/submodules/TelegramApi/Sources/Api15.swift index 8122ec4bb5..12f64c5e20 100644 --- a/submodules/TelegramApi/Sources/Api15.swift +++ b/submodules/TelegramApi/Sources/Api15.swift @@ -395,7 +395,7 @@ public extension Api { case messageActionStarGift(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64?, upgradeMsgId: Int32?, upgradeStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?) case messageActionStarGiftUnique(flags: Int32, gift: Api.StarGift, canExportAt: Int32?, transferStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, resaleStars: Int64?, canTransferAt: Int32?, canResellAt: Int32?) case messageActionSuggestProfilePhoto(photo: Api.Photo) - case messageActionSuggestedPostApproval(flags: Int32) + case messageActionSuggestedPostApproval(flags: Int32, rejectComment: String?, scheduleDate: Int32?, starsAmount: Int64?) case messageActionTodoAppendTasks(list: [Api.TodoItem]) case messageActionTodoCompletions(completed: [Int32], incompleted: [Int32]) case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) @@ -804,11 +804,14 @@ public extension Api { } photo.serialize(buffer, true) break - case .messageActionSuggestedPostApproval(let flags): + case .messageActionSuggestedPostApproval(let flags, let rejectComment, let scheduleDate, let starsAmount): if boxed { - buffer.appendInt32(866770590) + buffer.appendInt32(-1354584535) } serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {serializeString(rejectComment!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 4) != 0 {serializeInt64(starsAmount!, buffer: buffer, boxed: false)} break case .messageActionTodoAppendTasks(let list): if boxed { @@ -966,8 +969,8 @@ public extension Api { return ("messageActionStarGiftUnique", [("flags", flags as Any), ("gift", gift as Any), ("canExportAt", canExportAt as Any), ("transferStars", transferStars as Any), ("fromId", fromId as Any), ("peer", peer as Any), ("savedId", savedId as Any), ("resaleStars", resaleStars as Any), ("canTransferAt", canTransferAt as Any), ("canResellAt", canResellAt as Any)]) case .messageActionSuggestProfilePhoto(let photo): return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)]) - case .messageActionSuggestedPostApproval(let flags): - return ("messageActionSuggestedPostApproval", [("flags", flags as Any)]) + case .messageActionSuggestedPostApproval(let flags, let rejectComment, let scheduleDate, let starsAmount): + return ("messageActionSuggestedPostApproval", [("flags", flags as Any), ("rejectComment", rejectComment as Any), ("scheduleDate", scheduleDate as Any), ("starsAmount", starsAmount as Any)]) case .messageActionTodoAppendTasks(let list): return ("messageActionTodoAppendTasks", [("list", list as Any)]) case .messageActionTodoCompletions(let completed, let incompleted): @@ -1770,9 +1773,18 @@ public extension Api { public static func parse_messageActionSuggestedPostApproval(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() + var _2: String? + if Int(_1!) & Int(1 << 2) != 0 {_2 = parseString(reader) } + var _3: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_3 = reader.readInt32() } + var _4: Int64? + if Int(_1!) & Int(1 << 4) != 0 {_4 = reader.readInt64() } let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionSuggestedPostApproval(flags: _1!) + let _c2 = (Int(_1!) & Int(1 << 2) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageAction.messageActionSuggestedPostApproval(flags: _1!, rejectComment: _2, scheduleDate: _3, starsAmount: _4) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api38.swift b/submodules/TelegramApi/Sources/Api38.swift index b0d49118aa..b106c9431d 100644 --- a/submodules/TelegramApi/Sources/Api38.swift +++ b/submodules/TelegramApi/Sources/Api38.swift @@ -8909,14 +8909,15 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func toggleSuggestedPostApproval(flags: Int32, peer: Api.InputPeer, msgId: Int32, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func toggleSuggestedPostApproval(flags: Int32, peer: Api.InputPeer, msgId: Int32, scheduleDate: Int32?, rejectComment: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(806598987) + buffer.appendInt32(-2130229924) serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) serializeInt32(msgId, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.toggleSuggestedPostApproval", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("scheduleDate", String(describing: scheduleDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + if Int(flags) & Int(1 << 2) != 0 {serializeString(rejectComment!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.toggleSuggestedPostApproval", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("scheduleDate", String(describing: scheduleDate)), ("rejectComment", String(describing: rejectComment))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index 7d70e7a261..51d99d7c8e 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -228,7 +228,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .todoCompletions(completed: completed, incompleted: incompleted)) case let .messageActionTodoAppendTasks(list): return TelegramMediaAction(action: .todoAppendTasks(list.map { TelegramMediaTodo.Item(apiItem: $0) })) - case let .messageActionSuggestedPostApproval(flags): + case let .messageActionSuggestedPostApproval(flags, rejectComment, scheduleDate, starsAmount): let status: TelegramMediaActionType.SuggestedPostApprovalStatus if (flags & (1 << 0)) != 0 { let reason: TelegramMediaActionType.SuggestedPostApprovalStatus.RejectionReason @@ -237,9 +237,9 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe } else { reason = .generic } - status = .rejected(reason: reason) + status = .rejected(reason: reason, comment: rejectComment) } else { - status = .approved + status = .approved(timestamp: scheduleDate, amount: starsAmount ?? 0) } return TelegramMediaAction(action: .suggestedPostApprovalStatus(status: status)) } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index 3c4b210de6..b122036df2 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -117,13 +117,16 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case lowBalance } - case approved - case rejected(reason: RejectionReason) + case approved(timestamp: Int32?, amount: Int64) + case rejected(reason: RejectionReason, comment: String?) public init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("_t", orElse: 0) { case 0: - self = .approved + self = .approved( + timestamp: decoder.decodeOptionalInt32ForKey("ts"), + amount: decoder.decodeInt64ForKey("am", orElse: 0) + ) case 1: let reason: RejectionReason switch decoder.decodeInt32ForKey("rs", orElse: 0) { @@ -135,18 +138,24 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { assertionFailure() reason = .generic } - self = .rejected(reason: reason) + self = .rejected(reason: reason, comment: decoder.decodeOptionalStringForKey("com")) default: assertionFailure() - self = .rejected(reason: .generic) + self = .rejected(reason: .generic, comment: nil) } } public func encode(_ encoder: PostboxEncoder) { switch self { - case .approved: + case let .approved(timestamp, amount): encoder.encodeInt32(0, forKey: "_t") - case let .rejected(reason): + if let timestamp { + encoder.encodeInt32(timestamp, forKey: "ts") + } else { + encoder.encodeNil(forKey: "ts") + } + encoder.encodeInt64(amount, forKey: "am") + case let .rejected(reason, comment): encoder.encodeInt32(1, forKey: "_t") switch reason { case .generic: @@ -154,6 +163,11 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case .lowBalance: encoder.encodeInt32(1, forKey: "rs") } + if let comment { + encoder.encodeString(comment, forKey: "com") + } else { + encoder.encodeNil(forKey: "com") + } } } } @@ -356,7 +370,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { ) case 51: let status: SuggestedPostApprovalStatus? = decoder.decodeObjectForKey("st", decoder: { SuggestedPostApprovalStatus(decoder: $0) }) as? SuggestedPostApprovalStatus - self = .suggestedPostApprovalStatus(status: status ?? .rejected(reason: .generic)) + self = .suggestedPostApprovalStatus(status: status ?? .rejected(reason: .generic, comment: nil)) default: self = .unknown } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index a64692164a..b1e1a49b2d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -1588,13 +1588,18 @@ public extension TelegramEngine { return _internal_requestMessageAuthor(account: self.account, id: id) } - public func monoforumPerformSuggestedPostAction(id: EngineMessage.Id, approve: Bool, timestamp: Int32?) -> Signal { - return _internal_monoforumPerformSuggestedPostAction(account: self.account, id: id, approve: approve, timestamp: timestamp) + public enum MonoforumSuggestedPostAction { + case approve(timestamp: Int32?) + case reject(comment: String?) + } + + public func monoforumPerformSuggestedPostAction(id: EngineMessage.Id, action: MonoforumSuggestedPostAction) -> Signal { + return _internal_monoforumPerformSuggestedPostAction(account: self.account, id: id, action: action) } } } -func _internal_monoforumPerformSuggestedPostAction(account: Account, id: EngineMessage.Id, approve: Bool, timestamp: Int32?) -> Signal { +func _internal_monoforumPerformSuggestedPostAction(account: Account, id: EngineMessage.Id, action: TelegramEngine.Messages.MonoforumSuggestedPostAction) -> Signal { return account.postbox.transaction { transaction -> Api.InputPeer? in return transaction.getPeer(id.peerId).flatMap(apiInputPeer) } @@ -1607,13 +1612,22 @@ func _internal_monoforumPerformSuggestedPostAction(account: Account, id: EngineM } var flags: Int32 = 0 - if !approve { + var timestamp: Int32? + var rejectComment: String? + switch action { + case let .approve(timestampValue): + timestamp = timestampValue + if timestamp != nil { + flags |= 1 << 0 + } + case let .reject(commentValue): flags |= 1 << 1 + rejectComment = commentValue + if rejectComment != nil { + flags |= 1 << 2 + } } - if timestamp != nil { - flags |= 1 << 0 - } - return account.network.request(Api.functions.messages.toggleSuggestedPostApproval(flags: flags, peer: inputPeer, msgId: id.id, scheduleDate: timestamp)) + return account.network.request(Api.functions.messages.toggleSuggestedPostApproval(flags: flags, peer: inputPeer, msgId: id.id, scheduleDate: timestamp, rejectComment: rejectComment)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/BUILD index e487f7b72c..8f97f101b0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/BUILD @@ -31,6 +31,7 @@ swift_library( "//submodules/TelegramUI/Components/TextNodeWithEntities", "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/Markdown", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift index 657ab5dc3c..dabd04bc95 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift @@ -21,6 +21,7 @@ import InvisibleInkDustNode import TextNodeWithEntities import ChatMessageBubbleContentNode import ChatMessageItemCommon +import Markdown private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: Message, messageCount: Int? = nil, accountPeerId: PeerId, forForumOverview: Bool) -> NSAttributedString? { return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: EngineMessage(message), messageCount: messageCount, accountPeerId: accountPeerId, forChatList: false, forForumOverview: forForumOverview) @@ -29,6 +30,7 @@ private func attributedServiceMessageString(theme: ChatPresentationThemeData, st public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { public var expandHighlightingNode: LinkHighlightingNode? + public var titleNode: TextNode? public let labelNode: TextNodeWithEntities private var dustNode: InvisibleInkDustNode? public var backgroundNode: WallpaperBubbleBackgroundNode? @@ -155,6 +157,7 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { } override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeLabelLayout = TextNodeWithEntities.asyncLayout(self.labelNode) let cachedMaskBackgroundImage = self.cachedMaskBackgroundImage @@ -184,11 +187,14 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { var image: TelegramMediaImage? var story: TelegramMediaStory? + var suggestedPost: TelegramMediaActionType.SuggestedPostApprovalStatus? for media in item.message.media { if let action = media as? TelegramMediaAction { switch action.action { case let .photoUpdated(img): image = img + case let .suggestedPostApprovalStatus(status): + suggestedPost = status default: break } @@ -206,7 +212,94 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { updatedAttributedString = mutableString } - let (labelLayout, apply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: updatedAttributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + var textAlignment: NSTextAlignment = .center + + let primaryTextColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper).primaryText + + if let suggestedPost { + textAlignment = .left + + let channelName: String + if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, peer.isMonoForum, let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = item.message.peers[linkedMonoforumId] as? TelegramChannel { + channelName = EnginePeer(mainChannel).compactDisplayTitle + } else { + channelName = " " + } + + switch suggestedPost { + case let .approved(timestamp, amount): + //TODO:localize + let timeString = humanReadableStringForTimestamp(strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat, timestamp: timestamp ?? 0, alwaysShowTime: true, allowYesterday: false, format: HumanReadableStringFormat( + dateFormatString: { value in + return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_Date(value).string, ranges: []) + }, + tomorrowFormatString: { value in + return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TomorrowAt(value).string, ranges: []) + }, + todayFormatString: { value in + return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TodayAt(value).string, ranges: []) + }, + yesterdayFormatString: { value in + return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TodayAt(value).string, ranges: []) + } + )).string + + let amountString = amount == 1 ? "\(amount) Star" : "\(amount) Stars" + + let rawString = "šŸ“… Your post will be automatically published on **\(channelName)** **\(timeString)**.\n\nšŸ’° You have been charged \(amountString).\n\nāŒ› **\(channelName)** will receive your Stars once the post has been live for 24 hours.\n\nšŸ”„ If **\(channelName)** removes the post before it has been live for 24 hours, your Stars will be refunded." + updatedAttributedString = parseMarkdownIntoAttributedString(rawString, attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor), + bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: primaryTextColor), + link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor), + linkAttribute: { url in + return ("URL", url) + } + )) + case let .rejected(reason, comment): + let rawString: String + switch reason { + case .generic: + if let comment { + rawString = "**\(channelName)** declined your post with the following comment:\n\n" + comment + } else { + rawString = "**\(channelName)** declined your post." + } + case .lowBalance: + rawString = "**\(channelName)** was unable to post your message, because you did not have enough Stars." + } + updatedAttributedString = parseMarkdownIntoAttributedString(rawString, attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor), + bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: primaryTextColor), + link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor), + linkAttribute: { url in + return ("URL", url) + } + )) + } + } + + var titleLayoutAndApply: (TextNodeLayout, () -> TextNode)? + if let suggestedPost { + let rawString: String + switch suggestedPost { + case .approved: + rawString = "šŸ¤ Agreement Reached!" + case .rejected: + rawString = "Declined" + } + let titleString = parseMarkdownIntoAttributedString(rawString, attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.semibold(15.0), textColor: primaryTextColor), + bold: MarkdownAttributeSet(font: Font.bold(15.0), textColor: primaryTextColor), + link: MarkdownAttributeSet(font: Font.semibold(15.0), textColor: primaryTextColor), + linkAttribute: { url in + return ("URL", url) + } + )) + + titleLayoutAndApply = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: textAlignment, cutout: nil, insets: UIEdgeInsets())) + } + + let (labelLayout, apply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: updatedAttributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: textAlignment, cutout: nil, insets: UIEdgeInsets())) var labelRects = labelLayout.linesRects() if labelRects.count > 1 { @@ -231,20 +324,47 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { let backgroundMaskImage: (CGPoint, UIImage)? var backgroundMaskUpdated = false - if let (currentOffset, currentImage, currentRects) = cachedMaskBackgroundImage, currentRects == labelRects { - backgroundMaskImage = (currentOffset, currentImage) + if suggestedPost != nil { + backgroundMaskImage = nil + if cachedMaskBackgroundImage != nil { + backgroundMaskUpdated = true + } } else { - backgroundMaskImage = LinkHighlightingNode.generateImage(color: .white, inset: 0.0, innerRadius: 10.0, outerRadius: 10.0, rects: labelRects, useModernPathCalculation: false) - backgroundMaskUpdated = true + if let (currentOffset, currentImage, currentRects) = cachedMaskBackgroundImage, currentRects == labelRects { + backgroundMaskImage = (currentOffset, currentImage) + } else { + backgroundMaskImage = LinkHighlightingNode.generateImage(color: .white, inset: 0.0, innerRadius: 10.0, outerRadius: 10.0, rects: labelRects, useModernPathCalculation: false) + backgroundMaskUpdated = true + } } - var backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + 4.0) + var backgroundSize = CGSize(width: labelLayout.size.width, height: labelLayout.size.height) if let _ = image { backgroundSize.width = imageSize.width + 2.0 backgroundSize.height += imageSize.height + 10.0 } + + let titleSpacing: CGFloat = 18.0 + + var contentInsets = UIEdgeInsets() + var contentOuterInsets = UIEdgeInsets() + + if let titleLayoutAndApply { + backgroundSize.width = max(backgroundSize.width, titleLayoutAndApply.0.size.width) + backgroundSize.height += titleSpacing + titleLayoutAndApply.0.size.height + + contentInsets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0) + contentOuterInsets = UIEdgeInsets(top: 8.0, left: 0.0, bottom: 8.0, right: 0.0) + + backgroundSize.width += contentInsets.left + contentInsets.right + backgroundSize.height += contentInsets.top + contentInsets.bottom + } else { + backgroundSize.width += 8.0 + 8.0 + backgroundSize.height += 4.0 + } + return (backgroundSize.width, { boundingWidth in - return (CGSize(width: boundingWidth, height: backgroundSize.height), { [weak self] animation, synchronousLoads, _ in + return (CGSize(width: boundingWidth, height: backgroundSize.height + contentOuterInsets.top + contentOuterInsets.bottom), { [weak self] animation, synchronousLoads, _ in if let strongSelf = self { strongSelf.item = item @@ -330,7 +450,31 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { attemptSynchronous: synchronousLoads )) - let labelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((boundingWidth - labelLayout.size.width) / 2.0) - 1.0, y: image != nil ? 2.0 : floorToScreenPixels((backgroundSize.height - labelLayout.size.height) / 2.0) - 1.0), size: labelLayout.size) + let labelFrame: CGRect + let contentFrame: CGRect + + if let (titleLayout, titleApply) = titleLayoutAndApply { + contentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((boundingWidth - backgroundSize.width) * 0.5), y: contentOuterInsets.top), size: backgroundSize) + + let titleFrame = CGRect(origin: CGPoint(x: contentFrame.minX + floor((contentFrame.width - titleLayout.size.width) * 0.5), y: contentFrame.minY + contentInsets.top), size: titleLayout.size) + labelFrame = CGRect(origin: CGPoint(x: contentFrame.minX + contentInsets.left, y: titleFrame.maxY + titleSpacing), size: labelLayout.size) + + let titleNode = titleApply() + if strongSelf.titleNode !== titleNode { + strongSelf.titleNode?.removeFromSupernode() + strongSelf.titleNode = titleNode + strongSelf.addSubnode(titleNode) + titleNode.anchorPoint = CGPoint() + + titleNode.frame = titleFrame + } else { + animation.animator.updatePosition(layer: titleNode.layer, position: titleFrame.origin, completion: nil) + titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) + } + } else { + labelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((boundingWidth - labelLayout.size.width) / 2.0) - 1.0, y: image != nil ? 2.0 : floorToScreenPixels((backgroundSize.height - labelLayout.size.height) / 2.0) - 1.0), size: labelLayout.size) + contentFrame = labelFrame + } if story != nil { let leadingIconView: UIImageView @@ -395,7 +539,59 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.expandHighlightingNode = nil } - if let (offset, image) = backgroundMaskImage { + if suggestedPost != nil { + let backgroundFrame = contentFrame + + if item.context.sharedContext.energyUsageSettings.fullTranslucency { + if strongSelf.backgroundNode == nil { + if let backgroundNode = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { + strongSelf.backgroundNode = backgroundNode + backgroundNode.addSubnode(strongSelf.backgroundColorNode) + strongSelf.insertSubnode(backgroundNode, at: 0) + } + } + strongSelf.backgroundColorNode.isHidden = true + } else { + if strongSelf.backgroundMaskNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.backgroundMaskNode, at: 0) + } + } + + if let backgroundNode = strongSelf.backgroundNode { + backgroundNode.clipsToBounds = true + backgroundNode.cornerRadius = min(backgroundFrame.height * 0.5, 22.0) + backgroundNode.view.mask = nil + + animation.animator.updateFrame(layer: backgroundNode.layer, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: backgroundFrame.size), completion: nil) + + if let (rect, size) = strongSelf.absoluteRect { + strongSelf.updateAbsoluteRect(rect, within: size) + } + strongSelf.backgroundMaskNode.frame = CGRect(origin: CGPoint(), size: backgroundFrame.size) + strongSelf.backgroundMaskNode.layer.layerTintColor = nil + } else { + animation.animator.updateFrame(layer: strongSelf.backgroundMaskNode.layer, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: backgroundFrame.size), completion: nil) + strongSelf.backgroundMaskNode.layer.layerTintColor = selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper).cgColor + } + + strongSelf.backgroundMaskNode.image = nil + + animation.animator.updateFrame(layer: strongSelf.backgroundColorNode.layer, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size), completion: nil) + + strongSelf.cachedMaskBackgroundImage = nil + + switch strongSelf.visibility { + case .none: + strongSelf.labelNode.visibilityRect = nil + //strongSelf.spoilerTextNode?.visibilityRect = nil + case let .visible(_, subRect): + var subRect = subRect + subRect.origin.x = 0.0 + subRect.size.width = 10000.0 + strongSelf.labelNode.visibilityRect = subRect + //strongSelf.spoilerTextNode?.visibilityRect = subRect + } + } else if let (offset, image) = backgroundMaskImage { if item.context.sharedContext.energyUsageSettings.fullTranslucency { if strongSelf.backgroundNode == nil { if let backgroundNode = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { @@ -415,7 +611,7 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { if let backgroundNode = strongSelf.backgroundNode { if labelRects.count == 1 { backgroundNode.clipsToBounds = true - backgroundNode.cornerRadius = labelRects[0].height / 2.0 + backgroundNode.cornerRadius = min(32.0, labelRects[0].height / 2.0) backgroundNode.view.mask = nil } else { backgroundNode.clipsToBounds = false diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD index 0359206708..8f1dd1764c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD @@ -91,6 +91,7 @@ swift_library( "//submodules/TelegramUI/Components/LottieMetal", "//submodules/TelegramStringFormatting", "//submodules/AvatarNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index cff8f1701e..26ae6cf18f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -80,6 +80,7 @@ import AnimatedStickerNode import TelegramAnimatedStickerNode import LottieMetal import AvatarNode +import ChatMessageSuggestedPostInfoNode private struct BubbleItemAttributes { var index: Int? @@ -625,6 +626,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI private let shadowNode: ChatMessageShadowNode private var clippingNode: ChatMessageBubbleClippingNode + private var suggestedPostInfoNode: ChatMessageSuggestedPostInfoNode? + override public var extractedBackgroundNode: ASDisplayNode? { return self.shadowNode } @@ -1432,6 +1435,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI let weakSelf = Weak(self) + let makeSuggestedPostInfoNodeLayout: ChatMessageSuggestedPostInfoNode.AsyncLayout = ChatMessageSuggestedPostInfoNode.asyncLayout(self.suggestedPostInfoNode) + return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData) return ChatMessageBubbleItemNode.beginLayout( @@ -1454,6 +1459,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI unlockButtonLayout: unlockButtonLayout, mediaInfoLayout: mediaInfoLayout, mosaicStatusLayout: mosaicStatusLayout, + makeSuggestedPostInfoNodeLayout: makeSuggestedPostInfoNodeLayout, layoutConstants: layoutConstants, currentItem: currentItem, currentForwardInfo: currentForwardInfo, @@ -1482,6 +1488,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI unlockButtonLayout: (ChatMessageUnlockMediaNode.Arguments) -> (CGSize, (Bool) -> ChatMessageUnlockMediaNode), mediaInfoLayout: (ChatMessageStarsMediaInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageStarsMediaInfoNode), mosaicStatusLayout: (ChatMessageDateAndStatusNode.Arguments) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode)), + makeSuggestedPostInfoNodeLayout: ChatMessageSuggestedPostInfoNode.AsyncLayout, layoutConstants: ChatMessageItemLayoutConstants, currentItem: ChatMessageItem?, currentForwardInfo: (Peer?, String?)?, @@ -3039,7 +3046,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI var totalContentNodesHeight: CGFloat = 0.0 var currentContainerGroupOverlap: CGFloat = 0.0 var detachedContentNodesHeight: CGFloat = 0.0 - let additionalTopHeight: CGFloat = 0.0 + var additionalTopHeight: CGFloat = 0.0 var mosaicStatusOrigin: CGPoint? var unlockButtonPosition: CGPoint? @@ -3215,6 +3222,18 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } reactionButtonsSizeAndApply = reactionButtonsFinalize(maxContentWidth) } + + var suggestedPostInfoNodeLayout: (CGSize, () -> ChatMessageSuggestedPostInfoNode)? + for attribute in item.message.attributes { + if let _ = attribute as? SuggestedPostMessageAttribute { + let suggestedPostInfoNodeLayoutValue = makeSuggestedPostInfoNodeLayout(item, baseWidth) + suggestedPostInfoNodeLayout = suggestedPostInfoNodeLayoutValue + } + } + + if let suggestedPostInfoNodeLayout { + additionalTopHeight += 4.0 + suggestedPostInfoNodeLayout.0.height + 8.0 + } let minimalContentSize: CGSize if hideBackground { @@ -3352,6 +3371,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI avatarOffset: avatarOffset, hidesHeaders: hidesHeaders, disablesComments: disablesComments, + suggestedPostInfoNodeLayout: suggestedPostInfoNodeLayout, alignment: alignment, isSidePanelOpen: isSidePanelOpen ) @@ -3415,6 +3435,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI avatarOffset: CGFloat?, hidesHeaders: Bool, disablesComments: Bool, + suggestedPostInfoNodeLayout: (CGSize, () -> ChatMessageSuggestedPostInfoNode)?, alignment: ChatMessageBubbleContentAlignment, isSidePanelOpen: Bool ) -> Void { @@ -3499,6 +3520,22 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI let previousBackgroundFrame = strongSelf.backgroundNode.backgroundFrame strongSelf.backgroundNode.backgroundFrame = backgroundFrame + if let (suggestedPostInfoSize, suggestedPostInfoApply) = suggestedPostInfoNodeLayout { + let suggestedPostInfoNode = suggestedPostInfoApply() + if suggestedPostInfoNode !== strongSelf.suggestedPostInfoNode { + strongSelf.suggestedPostInfoNode?.removeFromSupernode() + strongSelf.suggestedPostInfoNode = suggestedPostInfoNode + strongSelf.mainContextSourceNode.contentNode.addSubnode(suggestedPostInfoNode) + + let suggestedPostInfoFrame = CGRect(origin: CGPoint(x: floor((params.width - suggestedPostInfoSize.width) * 0.5), y: 4.0), size: suggestedPostInfoSize) + suggestedPostInfoNode.frame = suggestedPostInfoFrame + //animation.animator.updateFrame(layer: suggestedPostInfoNode.layer, frame: suggestedPostInfoFrame, completion: nil) + } + } else if let suggestedPostInfoNode = strongSelf.suggestedPostInfoNode { + strongSelf.suggestedPostInfoNode = nil + suggestedPostInfoNode.removeFromSupernode() + } + if let avatarOffset { strongSelf.updateAttachedAvatarNodeOffset(offset: avatarOffset, transition: .animated(duration: 0.3, curve: .spring)) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/BUILD new file mode 100644 index 0000000000..f9e2a62062 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/BUILD @@ -0,0 +1,29 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageSuggestedPostInfoNode", + module_name = "ChatMessageSuggestedPostInfoNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/TextFormat", + "//submodules/AccountContext", + "//submodules/WallpaperBackgroundNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItem", + "//submodules/TelegramStringFormatting", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift new file mode 100644 index 0000000000..0f19af1025 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift @@ -0,0 +1,171 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import TextFormat +import AccountContext +import WallpaperBackgroundNode +import ChatMessageItem +import TelegramStringFormatting + +public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode { + private var titleNode: TextNode? + private var priceLabelNode: TextNode? + private var priceValueNode: TextNode? + private var timeLabelNode: TextNode? + private var timeValueNode: TextNode? + + private var backgroundNode: WallpaperBubbleBackgroundNode? + + override public init() { + super.init() + } + + public typealias AsyncLayout = (ChatMessageItem, CGFloat) -> (CGSize, () -> ChatMessageSuggestedPostInfoNode) + + public static func asyncLayout(_ node: ChatMessageSuggestedPostInfoNode?) -> (ChatMessageItem, CGFloat) -> (CGSize, () -> ChatMessageSuggestedPostInfoNode) { + let makeTitleLayout = TextNode.asyncLayout(node?.titleNode) + let makePriceLabelLayout = TextNode.asyncLayout(node?.priceLabelNode) + let makePriceValueLayout = TextNode.asyncLayout(node?.priceValueNode) + let makeTimeLabelLayout = TextNode.asyncLayout(node?.timeLabelNode) + let makeTimeValueLayout = TextNode.asyncLayout(node?.timeValueNode) + + return { item, maxWidth in + let insets = UIEdgeInsets( + top: 12.0, + left: 12.0, + bottom: 12.0, + right: 12.0 + ) + + let titleSpacing: CGFloat = 8.0 + let labelSpacing: CGFloat = 8.0 + let valuesVerticalSpacing: CGFloat = 2.0 + + var amount: Int64 = 0 + var timestamp: Int32? + + for attribute in item.message.attributes { + if let attribute = attribute as? SuggestedPostMessageAttribute { + amount = attribute.amount + timestamp = attribute.timestamp + } + } + + //TODO:localize + let amountString: String + if amount == 0 { + amountString = "Free" + } else if amount == 1 { + amountString = "1 Star" + } else { + amountString = "\(amount) Stars" + } + + var timestampString: String + if let timestamp { + timestampString = humanReadableStringForTimestamp(strings: item.presentationData.strings, dateTimeFormat: PresentationDateTimeFormat(), timestamp: timestamp, alwaysShowTime: true).string + if timestampString.count > 1 { + timestampString = String(timestampString[timestampString.startIndex]).capitalized + timestampString[timestampString.index(after: timestampString.startIndex)...] + } + } else { + timestampString = "Anytime" + } + + let serviceColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper) + + //TODO:localize + let titleText: String + 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." + } + + let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleText, font: Font.regular(13.0), textColor: serviceColor.primaryText), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + let priceLabelLayout = makePriceLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Price", font: Font.regular(13.0), textColor: serviceColor.primaryText.withMultipliedAlpha(0.5)), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let timeLabelLayout = makeTimeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Time", font: Font.regular(13.0), textColor: serviceColor.primaryText.withMultipliedAlpha(0.5)), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let priceValueLayout = makePriceValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: amountString, font: Font.semibold(13.0), textColor: serviceColor.primaryText), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let timeValueLayout = makeTimeValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: timestampString, font: Font.semibold(13.0), textColor: serviceColor.primaryText), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + var maxContentWidth: CGFloat = 0.0 + var contentHeight: CGFloat = 0.0 + + maxContentWidth = max(maxContentWidth, titleLayout.0.size.width) + + contentHeight += titleLayout.0.size.height + contentHeight += titleSpacing + + maxContentWidth = max(maxContentWidth, priceLabelLayout.0.size.width + labelSpacing + priceValueLayout.0.size.width) + contentHeight += priceLabelLayout.0.size.height + valuesVerticalSpacing + + maxContentWidth = max(maxContentWidth, timeLabelLayout.0.size.width + labelSpacing + timeValueLayout.0.size.width) + contentHeight += timeLabelLayout.0.size.height + + let size = CGSize(width: insets.left + insets.right + maxContentWidth, height: insets.top + insets.bottom + contentHeight) + + return (size, { + let node = node ?? ChatMessageSuggestedPostInfoNode() + + if node.backgroundNode == nil { + if let backgroundNode = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { + node.backgroundNode = backgroundNode + backgroundNode.layer.masksToBounds = true + backgroundNode.layer.cornerRadius = 15.0 + node.insertSubnode(backgroundNode, at: 0) + } + } + + if let backgroundNode = node.backgroundNode { + backgroundNode.frame = CGRect(origin: CGPoint(), size: size) + } + + let titleNode = titleLayout.1() + if node.titleNode !== titleNode { + node.titleNode = titleNode + node.addSubnode(titleNode) + } + let priceLabelNode = priceLabelLayout.1() + if node.priceLabelNode !== priceLabelNode { + node.priceLabelNode = priceLabelNode + node.addSubnode(priceLabelNode) + } + let priceValueNode = priceValueLayout.1() + if node.priceValueNode !== priceValueNode { + node.priceValueNode = priceValueNode + node.addSubnode(priceValueNode) + } + let timeLabelNode = timeLabelLayout.1() + if node.timeLabelNode !== timeLabelNode { + node.timeLabelNode = timeLabelNode + node.addSubnode(timeLabelNode) + } + let timeValueNode = timeValueLayout.1() + if node.timeValueNode !== timeValueNode { + node.timeValueNode = timeValueNode + node.addSubnode(timeValueNode) + } + + let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.0.size.width) * 0.5), y: insets.top), size: titleLayout.0.size) + titleNode.frame = titleFrame + + let priceLabelFrame = CGRect(origin: CGPoint(x: insets.left, y: titleFrame.maxY + titleSpacing), size: priceLabelLayout.0.size) + priceLabelNode.frame = priceLabelFrame + priceValueNode.frame = CGRect(origin: CGPoint(x: priceLabelFrame.maxX + labelSpacing, y: priceLabelFrame.minY), size: priceValueLayout.0.size) + + let timeLabelFrame = CGRect(origin: CGPoint(x: insets.left, y: priceLabelFrame.maxY + valuesVerticalSpacing), size: timeLabelLayout.0.size) + timeLabelNode.frame = timeLabelFrame + timeValueNode.frame = CGRect(origin: CGPoint(x: timeLabelFrame.maxX + labelSpacing, y: timeLabelFrame.minY), size: timeValueLayout.0.size) + + return node + }) + } + } +} diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 5f8cb0ab56..b1b10e9cf3 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -2331,13 +2331,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if message.effectivelyIncoming(strongSelf.context.account.peerId) { switch buttonType { case 0: - let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, approve: false, timestamp: nil).startStandalone() + let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .reject(comment: nil)).startStandalone() case 1: var timestamp: Int32? if attribute.timestamp == nil { timestamp = Int32(Date().timeIntervalSince1970) + 1 * 60 * 60 } - let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, approve: true, timestamp: timestamp).startStandalone() + let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: timestamp)).startStandalone() case 2: //suggest changes break