From 2c53fd2273a120c4c9717d7f0eda2121cc7508c0 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 13 Jun 2025 02:15:04 +0800 Subject: [PATCH 1/5] Update API --- submodules/TelegramApi/Sources/Api0.swift | 6 +- submodules/TelegramApi/Sources/Api15.swift | 40 +++++++-- submodules/TelegramApi/Sources/Api26.swift | 44 ++++++++++ submodules/TelegramApi/Sources/Api38.swift | 32 +++++-- .../Sources/Account/AccountManager.swift | 1 + .../ApiUtils/StoreMessage_Telegram.swift | 14 ++-- .../ApiUtils/TelegramMediaAction.swift | 18 +++- .../PendingMessages/EnqueueMessage.swift | 2 + .../StandaloneSendMessage.swift | 26 +++++- .../Sources/State/ApplyUpdateMessage.swift | 4 +- .../Sources/State/PendingMessageManager.swift | 17 +++- .../Sources/State/Serialization.swift | 2 +- .../Sources/State/UpdateMessageService.swift | 4 +- .../Sources/State/UpdatesApiUtils.swift | 12 +-- .../SuggestedPostMessageAttribute.swift | 84 +++++++++++++++++++ .../SyncCore_TelegramMediaAction.swift | 54 ++++++++++++ .../Messages/TelegramEngineMessages.swift | 38 +++++++++ .../Sources/ServiceMessageStrings.swift | 25 ++++++ .../Sources/ChatButtonKeyboardInputNode.swift | 4 +- .../Sources/ChatMessageBubbleItemNode.swift | 29 +++++++ .../ChatMessageGameBubbleContentNode.swift | 4 +- .../Sources/ChatMessageItemView.swift | 4 +- .../ChatRecentActionsControllerNode.swift | 8 +- .../Sources/ChatControllerInteraction.swift | 5 +- .../Sources/StarsWithdrawalScreen.swift | 3 +- .../Chat/ChatControllerLoadDisplayNode.swift | 8 +- .../Chat/ChatControllerMediaRecording.swift | 6 +- .../Sources/Chat/ChatControllerPaste.swift | 8 +- .../Chat/ChatMessageActionOptions.swift | 4 +- ...ChatMessageDisplaySendMessageOptions.swift | 2 +- .../TelegramUI/Sources/ChatController.swift | 64 +++++++++++--- .../Sources/ChatControllerNode.swift | 2 +- .../ChatControllerOpenAttachmentMenu.swift | 24 +++--- .../ChatPinnedMessageTitlePanelNode.swift | 4 +- 34 files changed, 510 insertions(+), 92 deletions(-) create mode 100644 submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 644cb83ce5..1f35056ff4 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -557,7 +557,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) } dict[1235637404] = { return Api.MediaArea.parse_mediaAreaWeather($0) } dict[-808853502] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) } - dict[-356721331] = { return Api.Message.parse_message($0) } + dict[-1743401272] = { return Api.Message.parse_message($0) } dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) } dict[2055212554] = { return Api.Message.parse_messageService($0) } dict[-872240531] = { return Api.MessageAction.parse_messageActionBoostApply($0) } @@ -607,6 +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[-940721021] = { return Api.MessageAction.parse_messageActionTodoAppendTasks($0) } dict[-864265079] = { return Api.MessageAction.parse_messageActionTodoCompletions($0) } dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) } @@ -999,6 +1000,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1870436597] = { return Api.StoryView.parse_storyViewPublicForward($0) } dict[-1116418231] = { return Api.StoryView.parse_storyViewPublicRepost($0) } dict[-1923523370] = { return Api.StoryViews.parse_storyViews($0) } + dict[-1779537299] = { return Api.SuggestedPost.parse_suggestedPost($0) } dict[1964978502] = { return Api.TextWithEntities.parse_textWithEntities($0) } dict[-1609668650] = { return Api.Theme.parse_theme($0) } dict[-94849324] = { return Api.ThemeSettings.parse_themeSettings($0) } @@ -2202,6 +2204,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.StoryViews: _1.serialize(buffer, boxed) + case let _1 as Api.SuggestedPost: + _1.serialize(buffer, boxed) case let _1 as Api.TextWithEntities: _1.serialize(buffer, boxed) case let _1 as Api.Theme: diff --git a/submodules/TelegramApi/Sources/Api15.swift b/submodules/TelegramApi/Sources/Api15.swift index 237813eda5..8122ec4bb5 100644 --- a/submodules/TelegramApi/Sources/Api15.swift +++ b/submodules/TelegramApi/Sources/Api15.swift @@ -60,15 +60,15 @@ public extension Api { } public extension Api { indirect enum Message: TypeConstructorDescription { - case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?, factcheck: Api.FactCheck?, reportDeliveryUntilDate: Int32?, paidMessageStars: Int64?) + case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?, factcheck: Api.FactCheck?, reportDeliveryUntilDate: Int32?, paidMessageStars: Int64?, suggestedPost: Api.SuggestedPost?) case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?) case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, savedPeerId: Api.Peer?, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction, reactions: Api.MessageReactions?, ttlPeriod: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck, let reportDeliveryUntilDate, let paidMessageStars): + case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck, let reportDeliveryUntilDate, let paidMessageStars, let suggestedPost): if boxed { - buffer.appendInt32(-356721331) + buffer.appendInt32(-1743401272) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags2, buffer: buffer, boxed: false) @@ -108,6 +108,7 @@ public extension Api { if Int(flags2) & Int(1 << 3) != 0 {factcheck!.serialize(buffer, true)} if Int(flags2) & Int(1 << 5) != 0 {serializeInt32(reportDeliveryUntilDate!, buffer: buffer, boxed: false)} if Int(flags2) & Int(1 << 6) != 0 {serializeInt64(paidMessageStars!, buffer: buffer, boxed: false)} + if Int(flags2) & Int(1 << 7) != 0 {suggestedPost!.serialize(buffer, true)} break case .messageEmpty(let flags, let id, let peerId): if boxed { @@ -137,8 +138,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck, let reportDeliveryUntilDate, let paidMessageStars): - return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("viaBusinessBotId", viaBusinessBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any), ("effect", effect as Any), ("factcheck", factcheck as Any), ("reportDeliveryUntilDate", reportDeliveryUntilDate as Any), ("paidMessageStars", paidMessageStars as Any)]) + case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck, let reportDeliveryUntilDate, let paidMessageStars, let suggestedPost): + return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("viaBusinessBotId", viaBusinessBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any), ("effect", effect as Any), ("factcheck", factcheck as Any), ("reportDeliveryUntilDate", reportDeliveryUntilDate as Any), ("paidMessageStars", paidMessageStars as Any), ("suggestedPost", suggestedPost as Any)]) case .messageEmpty(let flags, let id, let peerId): return ("messageEmpty", [("flags", flags as Any), ("id", id as Any), ("peerId", peerId as Any)]) case .messageService(let flags, let id, let fromId, let peerId, let savedPeerId, let replyTo, let date, let action, let reactions, let ttlPeriod): @@ -231,6 +232,10 @@ public extension Api { if Int(_2!) & Int(1 << 5) != 0 {_29 = reader.readInt32() } var _30: Int64? if Int(_2!) & Int(1 << 6) != 0 {_30 = reader.readInt64() } + var _31: Api.SuggestedPost? + if Int(_2!) & Int(1 << 7) != 0 {if let signature = reader.readInt32() { + _31 = Api.parse(reader, signature: signature) as? Api.SuggestedPost + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -261,8 +266,9 @@ public extension Api { let _c28 = (Int(_2!) & Int(1 << 3) == 0) || _28 != nil let _c29 = (Int(_2!) & Int(1 << 5) == 0) || _29 != nil let _c30 = (Int(_2!) & Int(1 << 6) == 0) || _30 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 { - return Api.Message.message(flags: _1!, flags2: _2!, id: _3!, fromId: _4, fromBoostsApplied: _5, peerId: _6!, savedPeerId: _7, fwdFrom: _8, viaBotId: _9, viaBusinessBotId: _10, replyTo: _11, date: _12!, message: _13!, media: _14, replyMarkup: _15, entities: _16, views: _17, forwards: _18, replies: _19, editDate: _20, postAuthor: _21, groupedId: _22, reactions: _23, restrictionReason: _24, ttlPeriod: _25, quickReplyShortcutId: _26, effect: _27, factcheck: _28, reportDeliveryUntilDate: _29, paidMessageStars: _30) + let _c31 = (Int(_2!) & Int(1 << 7) == 0) || _31 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 { + return Api.Message.message(flags: _1!, flags2: _2!, id: _3!, fromId: _4, fromBoostsApplied: _5, peerId: _6!, savedPeerId: _7, fwdFrom: _8, viaBotId: _9, viaBusinessBotId: _10, replyTo: _11, date: _12!, message: _13!, media: _14, replyMarkup: _15, entities: _16, views: _17, forwards: _18, replies: _19, editDate: _20, postAuthor: _21, groupedId: _22, reactions: _23, restrictionReason: _24, ttlPeriod: _25, quickReplyShortcutId: _26, effect: _27, factcheck: _28, reportDeliveryUntilDate: _29, paidMessageStars: _30, suggestedPost: _31) } else { return nil @@ -389,6 +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 messageActionTodoAppendTasks(list: [Api.TodoItem]) case messageActionTodoCompletions(completed: [Int32], incompleted: [Int32]) case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) @@ -797,6 +804,12 @@ public extension Api { } photo.serialize(buffer, true) break + case .messageActionSuggestedPostApproval(let flags): + if boxed { + buffer.appendInt32(866770590) + } + serializeInt32(flags, buffer: buffer, boxed: false) + break case .messageActionTodoAppendTasks(let list): if boxed { buffer.appendInt32(-940721021) @@ -953,6 +966,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 .messageActionTodoAppendTasks(let list): return ("messageActionTodoAppendTasks", [("list", list as Any)]) case .messageActionTodoCompletions(let completed, let incompleted): @@ -1752,6 +1767,17 @@ public extension Api { return nil } } + public static func parse_messageActionSuggestedPostApproval(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionSuggestedPostApproval(flags: _1!) + } + else { + return nil + } + } public static func parse_messageActionTodoAppendTasks(_ reader: BufferReader) -> MessageAction? { var _1: [Api.TodoItem]? if let _ = reader.readInt32() { diff --git a/submodules/TelegramApi/Sources/Api26.swift b/submodules/TelegramApi/Sources/Api26.swift index a1aa3902be..265d72472c 100644 --- a/submodules/TelegramApi/Sources/Api26.swift +++ b/submodules/TelegramApi/Sources/Api26.swift @@ -530,6 +530,50 @@ public extension Api { } } +public extension Api { + enum SuggestedPost: TypeConstructorDescription { + case suggestedPost(flags: Int32, starsAmount: Int64, scheduleDate: Int32?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .suggestedPost(let flags, let starsAmount, let scheduleDate): + if boxed { + buffer.appendInt32(-1779537299) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(starsAmount, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .suggestedPost(let flags, let starsAmount, let scheduleDate): + return ("suggestedPost", [("flags", flags as Any), ("starsAmount", starsAmount as Any), ("scheduleDate", scheduleDate as Any)]) + } + } + + public static func parse_suggestedPost(_ reader: BufferReader) -> SuggestedPost? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.SuggestedPost.suggestedPost(flags: _1!, starsAmount: _2!, scheduleDate: _3) + } + else { + return nil + } + } + + } +} public extension Api { enum TextWithEntities: TypeConstructorDescription { case textWithEntities(text: String, entities: [Api.MessageEntity]) diff --git a/submodules/TelegramApi/Sources/Api38.swift b/submodules/TelegramApi/Sources/Api38.swift index 1c436d111e..b0d49118aa 100644 --- a/submodules/TelegramApi/Sources/Api38.swift +++ b/submodules/TelegramApi/Sources/Api38.swift @@ -8238,9 +8238,9 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func sendMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, media: Api.InputMedia, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, allowPaidStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func sendMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, media: Api.InputMedia, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-1521431176) + buffer.appendInt32(-1403659839) serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} @@ -8258,7 +8258,8 @@ public extension Api.functions.messages { if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} if Int(flags) & Int(1 << 18) != 0 {serializeInt64(effect!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 21) != 0 {serializeInt64(allowPaidStars!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.sendMedia", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("media", String(describing: media)), ("message", String(describing: message)), ("randomId", String(describing: randomId)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut)), ("effect", String(describing: effect)), ("allowPaidStars", String(describing: allowPaidStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + if Int(flags) & Int(1 << 22) != 0 {suggestedPost!.serialize(buffer, true)} + return (FunctionDescription(name: "messages.sendMedia", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("media", String(describing: media)), ("message", String(describing: message)), ("randomId", String(describing: randomId)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut)), ("effect", String(describing: effect)), ("allowPaidStars", String(describing: allowPaidStars)), ("suggestedPost", String(describing: suggestedPost))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { @@ -8269,9 +8270,9 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func sendMessage(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, allowPaidStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func sendMessage(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-68013046) + buffer.appendInt32(-33170278) serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)} @@ -8288,7 +8289,8 @@ public extension Api.functions.messages { if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} if Int(flags) & Int(1 << 18) != 0 {serializeInt64(effect!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 21) != 0 {serializeInt64(allowPaidStars!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.sendMessage", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("message", String(describing: message)), ("randomId", String(describing: randomId)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut)), ("effect", String(describing: effect)), ("allowPaidStars", String(describing: allowPaidStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + if Int(flags) & Int(1 << 22) != 0 {suggestedPost!.serialize(buffer, true)} + return (FunctionDescription(name: "messages.sendMessage", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("replyTo", String(describing: replyTo)), ("message", String(describing: message)), ("randomId", String(describing: randomId)), ("replyMarkup", String(describing: replyMarkup)), ("entities", String(describing: entities)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut)), ("effect", String(describing: effect)), ("allowPaidStars", String(describing: allowPaidStars)), ("suggestedPost", String(describing: suggestedPost))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { @@ -8906,6 +8908,24 @@ 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) { + let buffer = Buffer() + buffer.appendInt32(806598987) + 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 + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} public extension Api.functions.messages { static func toggleTodoCompleted(peer: Api.InputPeer, msgId: Int32, completed: [Int32], incompleted: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index cd25ec6b63..b4ec088ce9 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -236,6 +236,7 @@ private var declaredEncodables: Void = { declareEncodable(TelegramMediaTodo.self, f: { TelegramMediaTodo(decoder: $0) }) declareEncodable(TelegramMediaTodo.Item.self, f: { TelegramMediaTodo.Item(decoder: $0) }) declareEncodable(TelegramMediaTodo.Completion.self, f: { TelegramMediaTodo.Completion(decoder: $0) }) + declareEncodable(SuggestedPostMessageAttribute.self, f: { SuggestedPostMessageAttribute(decoder: $0) }) return }() diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 32f998d09c..d482422388 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -128,7 +128,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { switch messsage { - case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let chatPeerId = messagePeerId return chatPeerId.peerId case let .messageEmpty(_, _, peerId): @@ -144,7 +144,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { switch message { - case let .message(_, _, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, viaBusinessBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, viaBusinessBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = chatPeerId.peerId var result = [peerId] @@ -232,7 +232,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { } switch action { - case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionGiftStars, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionGiveawayLaunch, .messageActionGiveawayResults, .messageActionBoostApply, .messageActionRequestedPeerSentMe, .messageActionStarGift, .messageActionStarGiftUnique, .messageActionPaidMessagesRefunded, .messageActionPaidMessagesPrice, .messageActionTodoCompletions, .messageActionTodoAppendTasks: + case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionGiftStars, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionGiveawayLaunch, .messageActionGiveawayResults, .messageActionBoostApply, .messageActionRequestedPeerSentMe, .messageActionStarGift, .messageActionStarGiftUnique, .messageActionPaidMessagesRefunded, .messageActionPaidMessagesPrice, .messageActionTodoCompletions, .messageActionTodoAppendTasks, .messageActionSuggestedPostApproval: break case let .messageActionChannelMigrateFrom(_, chatId): result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))) @@ -279,7 +279,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: ReferencedReplyMessageIds, generalIds: [MessageId])? { switch message { - case let .message(_, _, id, _, _, chatPeerId, _, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, id, _, _, chatPeerId, _, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): if let replyTo = replyTo { let peerId: PeerId = chatPeerId.peerId @@ -691,7 +691,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes extension StoreMessage { convenience init?(apiMessage: Api.Message, accountPeerId: PeerId, peerIsForum: Bool, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { switch apiMessage { - case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId, factCheck, reportDeliveryUntilDate, paidMessageStars): + case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId, factCheck, reportDeliveryUntilDate, paidMessageStars, suggestedPost): var attributes: [MessageAttribute] = [] if (flags2 & (1 << 4)) != 0 { @@ -1011,6 +1011,10 @@ extension StoreMessage { attributes.append(FactCheckMessageAttribute(content: content, hash: hash)) } } + + if let suggestedPost { + attributes.append(SuggestedPostMessageAttribute(apiSuggestedPost: suggestedPost)) + } var storeFlags = StoreMessageFlags() diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index 9956dc94b8..7d70e7a261 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -206,7 +206,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe let isMissed = (flags & (1 << 0)) != 0 let isActive = (flags & (1 << 1)) != 0 let isVideo = (flags & (1 << 4)) != 0 - + var mappedFlags = TelegramMediaActionType.ConferenceCall.Flags() if isMissed { mappedFlags.insert(.isMissed) @@ -217,7 +217,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe if isVideo { mappedFlags.insert(.isVideo) } - + return TelegramMediaAction(action: .conferenceCall(TelegramMediaActionType.ConferenceCall( callId: callId, duration: duration, @@ -228,6 +228,20 @@ 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): + let status: TelegramMediaActionType.SuggestedPostApprovalStatus + if (flags & (1 << 0)) != 0 { + let reason: TelegramMediaActionType.SuggestedPostApprovalStatus.RejectionReason + if (flags & (1 << 0)) != 1 { + reason = .lowBalance + } else { + reason = .generic + } + status = .rejected(reason: reason) + } else { + status = .approved + } + return TelegramMediaAction(action: .suggestedPostApprovalStatus(status: status)) } } diff --git a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift index 360e066bee..20ae224114 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift @@ -256,6 +256,8 @@ private func filterMessageAttributesForOutgoingMessage(_ attributes: [MessageAtt return true case _ as PaidStarsMessageAttribute: return true + case _ as SuggestedPostMessageAttribute: + return true default: return false } diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift index 39add91411..310cbc2656 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift @@ -343,6 +343,7 @@ private func sendUploadedMessageContent( var sendAsPeerId: PeerId? var bubbleUpEmojiOrStickersets = false var allowPaidStars: Int64? + var suggestedPost: Api.SuggestedPost? var flags: Int32 = 0 @@ -382,6 +383,8 @@ private func sendUploadedMessageContent( videoTimestamp = attribute.timestamp } else if let attribute = attribute as? PaidStarsMessageAttribute { allowPaidStars = attribute.stars.value + } else if let attribute = attribute as? SuggestedPostMessageAttribute { + suggestedPost = attribute.apiSuggestedPost() } } @@ -442,7 +445,11 @@ private func sendUploadedMessageContent( replyTo = .inputReplyToMonoForum(monoforumPeerId: monoforumPeerId) } - sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars), info: .acknowledgement, tag: dependencyTag) + if suggestedPost != nil { + flags |= 1 << 22 + } + + sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), info: .acknowledgement, tag: dependencyTag) case let .media(inputMedia, text): if bubbleUpEmojiOrStickersets { flags |= Int32(1 << 15) @@ -466,8 +473,12 @@ private func sendUploadedMessageContent( replyTo = .inputReplyToStory(peer: inputPeer, storyId: replyToStoryId.id) } } + + if suggestedPost != nil { + flags |= 1 << 22 + } - sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) |> map(NetworkRequestResult.result) case let .forward(sourceInfo): if topMsgId != nil { @@ -617,6 +628,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M var scheduleTime: Int32? var sendAsPeerId: PeerId? var allowPaidStars: Int64? + var suggestedPost: Api.SuggestedPost? var flags: Int32 = 0 flags |= (1 << 7) @@ -643,6 +655,8 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M sendAsPeerId = attribute.peerId } else if let attribute = attribute as? PaidStarsMessageAttribute { allowPaidStars = attribute.stars.value + } else if let attribute = attribute as? SuggestedPostMessageAttribute { + suggestedPost = attribute.apiSuggestedPost() } } @@ -679,7 +693,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M replyTo = .inputReplyToMessage(flags: flags, replyToMsgId: threadId, topMsgId: threadId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil, monoforumPeerId: nil) } - sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars)) + sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: nil)) |> `catch` { _ -> Signal in return .complete() } @@ -700,7 +714,11 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M replyTo = .inputReplyToMessage(flags: flags, replyToMsgId: threadId, topMsgId: threadId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil, monoforumPeerId: nil) } - sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars)) + if suggestedPost != nil { + flags |= 1 << 22 + } + + sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost)) |> `catch` { _ -> Signal in return .complete() } diff --git a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift index 77627e29b3..8f8303124e 100644 --- a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift @@ -104,7 +104,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes var updatedTimestamp: Int32? if let apiMessage = apiMessage { switch apiMessage { - case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): updatedTimestamp = date case .messageEmpty: break @@ -400,7 +400,7 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage } else if let message = messages.first, let apiMessage = result.messages.first { if message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { namespace = Namespaces.Message.ScheduledCloud - } else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 { + } else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 { namespace = Namespaces.Message.ScheduledCloud } } diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index 82427e6b68..4062710eb2 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -1349,6 +1349,7 @@ public final class PendingMessageManager { var quickReply: OutgoingQuickReplyMessageAttribute? var messageEffect: EffectMessageAttribute? var allowPaidStars: Int64? + var suggestedPost: Api.SuggestedPost? var flags: Int32 = 0 @@ -1403,6 +1404,8 @@ public final class PendingMessageManager { videoTimestamp = attribute.timestamp } else if let attribute = attribute as? PaidStarsMessageAttribute { allowPaidStars = attribute.stars.value + } else if let attribute = attribute as? SuggestedPostMessageAttribute { + suggestedPost = attribute.apiSuggestedPost() } } @@ -1520,8 +1523,11 @@ public final class PendingMessageManager { if let _ = allowPaidStars { flags |= 1 << 21 } + if let _ = suggestedPost { + flags |= 1 << 22 + } - sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId, allowPaidStars: allowPaidStars), info: .acknowledgement, tag: dependencyTag) + sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), info: .acknowledgement, tag: dependencyTag) case let .media(inputMedia, text): if bubbleUpEmojiOrStickersets { flags |= Int32(1 << 15) @@ -1613,8 +1619,11 @@ public final class PendingMessageManager { if let _ = allowPaidStars { flags |= 1 << 21 } + if let _ = suggestedPost { + flags |= 1 << 22 + } - sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId, allowPaidStars: allowPaidStars), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) |> map(NetworkRequestResult.result) case let .forward(sourceInfo): var topMsgId: Int32? @@ -1897,7 +1906,7 @@ public final class PendingMessageManager { if message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { isScheduled = true } - if case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage { + if case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage { if (flags2 & (1 << 4)) != 0 { isScheduled = true } @@ -1941,7 +1950,7 @@ public final class PendingMessageManager { namespace = Namespaces.Message.QuickReplyCloud } else if let apiMessage = result.messages.first, message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { namespace = Namespaces.Message.ScheduledCloud - } else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 { + } else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 { namespace = Namespaces.Message.ScheduledCloud } } diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index a14e1baec0..95b93fef4c 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 205 + return 206 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift index f38b92fa02..93a88bc9c9 100644 --- a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift +++ b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift @@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService { self.putNext(groups) } case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod): - let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil) + let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { @@ -74,7 +74,7 @@ class UpdateMessageService: NSObject, MTMessageService { let generatedPeerId = Api.Peer.peerUser(userId: userId) - let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil) + let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index f2291524a3..3b7de380ec 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -104,7 +104,7 @@ extension Api.MessageMedia { extension Api.Message { var rawId: Int32 { switch self { - case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return id case let .messageEmpty(_, id, _): return id @@ -115,7 +115,7 @@ extension Api.Message { func id(namespace: MessageId.Namespace = Namespaces.Message.Cloud) -> MessageId? { switch self { - case let .message(_, flags2, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, flags2, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): var namespace = namespace if (flags2 & (1 << 4)) != 0 { namespace = Namespaces.Message.ScheduledCloud @@ -136,7 +136,7 @@ extension Api.Message { var peerId: PeerId? { switch self { - case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = messagePeerId.peerId return peerId case let .messageEmpty(_, _, peerId): @@ -149,7 +149,7 @@ extension Api.Message { var timestamp: Int32? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return date case let .messageService(_, _, _, _, _, _, date, _, _, _): return date @@ -160,7 +160,7 @@ extension Api.Message { var preCachedResources: [(MediaResource, Data)]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return media?.preCachedResources default: return nil @@ -169,7 +169,7 @@ extension Api.Message { var preCachedStories: [StoryId: Api.StoryItem]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return media?.preCachedStories default: return nil diff --git a/submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift new file mode 100644 index 0000000000..d1f4c2b1e0 --- /dev/null +++ b/submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift @@ -0,0 +1,84 @@ +import Foundation +import Postbox +import TelegramApi + +public final class SuggestedPostMessageAttribute: Equatable, MessageAttribute { + public enum State: Int32 { + case accepted = 0 + case rejected = 1 + } + + public let amount: Int64 + public let timestamp: Int32? + public let state: State? + + public init(amount: Int64, timestamp: Int32?, state: State?) { + self.amount = amount + self.timestamp = timestamp + self.state = state + } + + required public init(decoder: PostboxDecoder) { + self.amount = decoder.decodeInt64ForKey("am", orElse: 0) + self.timestamp = decoder.decodeOptionalInt32ForKey("ts") + self.state = decoder.decodeOptionalInt32ForKey("st").flatMap(State.init(rawValue:)) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt64(self.amount, forKey: "am") + if let timestamp = self.timestamp { + encoder.encodeInt32(timestamp, forKey: "ts") + } else { + encoder.encodeNil(forKey: "ts") + } + if let state = self.state { + encoder.encodeInt32(state.rawValue, forKey: "st") + } else { + encoder.encodeNil(forKey: "st") + } + } + + public static func ==(lhs: SuggestedPostMessageAttribute, rhs: SuggestedPostMessageAttribute) -> Bool { + if lhs.amount != rhs.amount { + return false + } + if lhs.timestamp != rhs.timestamp { + return false + } + if lhs.state != rhs.state { + return false + } + return true + } +} + +extension SuggestedPostMessageAttribute { + convenience init(apiSuggestedPost: Api.SuggestedPost) { + switch apiSuggestedPost { + case let .suggestedPost(flags, starsAmount, scheduleDate): + var state: State? + if (flags & (1 << 1)) != 0 { + state = .accepted + } else if (flags & (1 << 2)) != 0 { + state = .rejected + } + self.init(amount: starsAmount, timestamp: scheduleDate, state: state) + } + } + + func apiSuggestedPost() -> Api.SuggestedPost { + var flags: Int32 = 0 + if let state = self.state { + switch state { + case .accepted: + flags |= 1 << 1 + case .rejected: + flags |= 1 << 2 + } + } + if self.timestamp != nil { + flags |= 1 << 0 + } + return .suggestedPost(flags: flags, starsAmount: self.amount, scheduleDate: self.timestamp) + } +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index 205308ab5a..3c4b210de6 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -111,6 +111,53 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { } } + public enum SuggestedPostApprovalStatus: PostboxCoding, Equatable { + public enum RejectionReason { + case generic + case lowBalance + } + + case approved + case rejected(reason: RejectionReason) + + public init(decoder: PostboxDecoder) { + switch decoder.decodeInt32ForKey("_t", orElse: 0) { + case 0: + self = .approved + case 1: + let reason: RejectionReason + switch decoder.decodeInt32ForKey("rs", orElse: 0) { + case 0: + reason = .generic + case 1: + reason = .lowBalance + default: + assertionFailure() + reason = .generic + } + self = .rejected(reason: reason) + default: + assertionFailure() + self = .rejected(reason: .generic) + } + } + + public func encode(_ encoder: PostboxEncoder) { + switch self { + case .approved: + encoder.encodeInt32(0, forKey: "_t") + case let .rejected(reason): + encoder.encodeInt32(1, forKey: "_t") + switch reason { + case .generic: + encoder.encodeInt32(0, forKey: "rs") + case .lowBalance: + encoder.encodeInt32(1, forKey: "rs") + } + } + } + } + case unknown case groupCreated(title: String) case addedMembers(peerIds: [PeerId]) @@ -162,6 +209,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case conferenceCall(ConferenceCall) case todoCompletions(completed: [Int32], incompleted: [Int32]) case todoAppendTasks([TelegramMediaTodo.Item]) + case suggestedPostApprovalStatus(status: SuggestedPostApprovalStatus) public init(decoder: PostboxDecoder) { let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) @@ -306,6 +354,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { self = .todoAppendTasks( decoder.decodeObjectArrayWithDecoderForKey("tasks") ) + case 51: + let status: SuggestedPostApprovalStatus? = decoder.decodeObjectForKey("st", decoder: { SuggestedPostApprovalStatus(decoder: $0) }) as? SuggestedPostApprovalStatus + self = .suggestedPostApprovalStatus(status: status ?? .rejected(reason: .generic)) default: self = .unknown } @@ -716,6 +767,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case let .todoAppendTasks(tasks): encoder.encodeInt32(50, forKey: "_rawValue") encoder.encodeObjectArray(tasks, forKey: "tasks") + case let .suggestedPostApprovalStatus(status): + encoder.encodeInt32(51, forKey: "_rawValue") + encoder.encodeObject(status, forKey: "st") } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 3f878d31d1..a64692164a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -1587,5 +1587,43 @@ public extension TelegramEngine { public func requestMessageAuthor(id: EngineMessage.Id) -> Signal { 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) + } + } +} + +func _internal_monoforumPerformSuggestedPostAction(account: Account, id: EngineMessage.Id, approve: Bool, timestamp: Int32?) -> Signal { + return account.postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(id.peerId).flatMap(apiInputPeer) + } + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer else { + return .complete() + } + if id.namespace != Namespaces.Message.Cloud { + return .complete() + } + + var flags: Int32 = 0 + if !approve { + flags |= 1 << 1 + } + if timestamp != nil { + flags |= 1 << 0 + } + return account.network.request(Api.functions.messages.toggleSuggestedPostApproval(flags: flags, peer: inputPeer, msgId: id.id, scheduleDate: timestamp)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { updates -> Signal in + if let updates { + account.stateManager.addUpdates(updates) + } + + return .complete() + } } } diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 6d4e5c83b2..1272dc5406 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1380,6 +1380,31 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: attributes) } + case let .suggestedPostApprovalStatus(status): + //TODO:localize + var messageText = "" + for attribute in message.attributes { + if let attribute = attribute as? ReplyMessageAttribute, let message = message.associatedMessages[attribute.messageId] { + messageText = message.text + } + } + + let string: String + switch status { + case .approved: + if messageText.isEmpty { + string = "Your message was approved" + } else { + string = "Your message \"\(messageText)\" was approved" + } + case .rejected: + if messageText.isEmpty { + string = "Your message was rejected" + } else { + string = "Your message \"\(messageText)\" was rejected" + } + } + attributedString = NSAttributedString(string: string, font: titleFont, textColor: primaryTextColor) case .unknown: attributedString = nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatButtonKeyboardInputNode/Sources/ChatButtonKeyboardInputNode.swift b/submodules/TelegramUI/Components/Chat/ChatButtonKeyboardInputNode/Sources/ChatButtonKeyboardInputNode.swift index 5383690a2c..f741f5fb6b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatButtonKeyboardInputNode/Sources/ChatButtonKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatButtonKeyboardInputNode/Sources/ChatButtonKeyboardInputNode.swift @@ -385,11 +385,11 @@ public final class ChatButtonKeyboardInputNode: ChatInputNode { self.controllerInteraction.shareAccountContact() case .openWebApp: if let message = self.message { - self.controllerInteraction.requestMessageActionCallback(message.id, nil, true, false) + self.controllerInteraction.requestMessageActionCallback(message, nil, true, false) } case let .callback(requiresPassword, data): if let message = self.message { - self.controllerInteraction.requestMessageActionCallback(message.id, data, false, requiresPassword) + self.controllerInteraction.requestMessageActionCallback(message, data, false, requiresPassword) } case let .switchInline(samePeer, query, _): if let message = message { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 26e1075791..cff8f1701e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -2789,6 +2789,35 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI 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, item.chatLocation.threadId != nil, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = item.message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething), 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 + + 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: MemoryBuffer(data: Data(bytes: &buttonDecline, count: 1)))), + ReplyMarkupButton(title: "Approve", titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: MemoryBuffer(data: Data(bytes: &buttonApprove, count: 1)))) + ]), + ReplyMarkupRow(buttons: [ + ReplyMarkupButton(title: "Suggest Changes", titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: MemoryBuffer(data: Data(bytes: &buttonSuggestChanges, count: 1)))) + ]) + ], + flags: [], + placeholder: nil + ), 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) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift index 0410d0d68c..98475296cd 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift @@ -28,14 +28,14 @@ public final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNod self.addSubnode(self.contentNode) self.contentNode.openMedia = { [weak self] _ in if let strongSelf = self, let item = strongSelf.item { - item.controllerInteraction.requestMessageActionCallback(item.message.id, nil, true, false) + item.controllerInteraction.requestMessageActionCallback(item.message, nil, true, false) } } } override public func accessibilityActivate() -> Bool { if let item = self.item { - item.controllerInteraction.requestMessageActionCallback(item.message.id, nil, true, false) + item.controllerInteraction.requestMessageActionCallback(item.message, nil, true, false) } return true } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift index b8bcac5dac..839e46afd1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift @@ -821,9 +821,9 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { case .requestPhone: item.controllerInteraction.shareAccountContact() case .openWebApp: - item.controllerInteraction.requestMessageActionCallback(item.message.id, nil, true, false) + item.controllerInteraction.requestMessageActionCallback(item.message, nil, true, false) case let .callback(requiresPassword, data): - item.controllerInteraction.requestMessageActionCallback(item.message.id, data, false, requiresPassword) + item.controllerInteraction.requestMessageActionCallback(item.message, data, false, requiresPassword) case let .switchInline(samePeer, query, peerTypes): var botPeer: Peer? diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index 0ba487ee2f..3b6716dca2 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -316,14 +316,14 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always, animated: true).startStandalone() } }, tapMessage: nil, clickThroughMessage: { _, _ in }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false - }, requestMessageActionCallback: { [weak self] messageId, _, _, _ in + }, requestMessageActionCallback: { [weak self] message, _, _, _ in guard let self else { return } - if self.expandedDeletedMessages.contains(messageId) { - self.expandedDeletedMessages.remove(messageId) + if self.expandedDeletedMessages.contains(message.id) { + self.expandedDeletedMessages.remove(message.id) } else { - self.expandedDeletedMessages.insert(messageId) + self.expandedDeletedMessages.insert(message.id) } }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { [weak self] url in self?.openUrl(url.url, progress: url.progress) diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index f2d09c2b4a..98aa86bcde 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -3,6 +3,7 @@ import UIKit import Postbox import SwiftSignalKit import AsyncDisplayKit +import Postbox import TelegramCore import Display import TelegramUIPreferences @@ -192,7 +193,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public let sendEmoji: (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void public let sendGif: (FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool public let sendBotContextResultAsGif: (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool, Bool) -> Bool - public let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool, Bool) -> Void + public let requestMessageActionCallback: (Message, MemoryBuffer?, Bool, Bool) -> Void public let requestMessageActionUrlAuth: (String, MessageActionUrlSubject) -> Void public let activateSwitchInline: (PeerId?, String, ReplyMarkupButtonAction.PeerTypes?) -> Void public let openUrl: (OpenUrl) -> Void @@ -356,7 +357,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol sendEmoji: @escaping (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void, sendGif: @escaping (FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool, sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool, Bool) -> Bool, - requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void, + requestMessageActionCallback: @escaping (Message, MemoryBuffer?, Bool, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageActionUrlSubject) -> Void, activateSwitchInline: @escaping (PeerId?, String, ReplyMarkupButtonAction.PeerTypes?) -> Void, openUrl: @escaping (OpenUrl) -> Void, diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index c9e34fe12f..5e5d6007fe 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -164,9 +164,10 @@ private final class SheetContent: CombinedComponent { //TODO:localize titleString = "Suggest Terms" amountTitle = "ENTER A PRICE IN STARS" - amountPlaceholder = environment.strings.Stars_SendMessage_AdjustmentPlaceholder + amountPlaceholder = "Price" minAmount = StarsAmount(value: 1, nanos: 0) + //TODO:release maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0) } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index d38fb8b18b..b7ca2fcc50 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -1592,7 +1592,7 @@ extension ChatControllerImpl { }) } } else { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) }) }, completion: { t in + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) }) }, completion: { t in completion(t, {}) }) } @@ -2338,7 +2338,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreviews([]) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreviews([]) } }) } }, nil) @@ -2362,7 +2362,7 @@ extension ChatControllerImpl { } self.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreviews([]) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreviews([]) } }) if !self.presentationInterfaceState.isPremium { @@ -2378,7 +2378,7 @@ extension ChatControllerImpl { return } self.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreviews([]) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreviews([]) } }) }, nil) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift index 44f7997ac0..b25bc467a6 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift @@ -227,7 +227,7 @@ extension ChatControllerImpl { self.chatDisplayNode.collapseInput() self.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedMediaDraftState(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedMediaDraftState(nil).withUpdatedPostSuggestionState(nil) } }) } }, usedCorrelationId ? correlationId : nil) @@ -393,7 +393,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, usedCorrelationId ? correlationId : nil) @@ -718,7 +718,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedMediaDraftState(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedMediaDraftState(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) strongSelf.updateDownButtonVisibility() diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift index ca9c30f2cd..76b71ea164 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift @@ -60,7 +60,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) @@ -83,7 +83,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) @@ -118,7 +118,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) @@ -141,7 +141,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index e8c0d89a5f..0c013ef3d7 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -481,7 +481,7 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch } var replySubject = replySubject replySubject.quote = nil - selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withoutSelectionState() }).updatedSearch(nil) }) + selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil).withoutSelectionState() }).updatedSearch(nil) }) }))) } @@ -675,7 +675,7 @@ func moveReplyToChat(selfController: ChatControllerImpl, peerId: EnginePeer.Id, guard let selfController else { return } - selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withoutSelectionState() }) }) + selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil).withoutSelectionState() }) }) let navigationController: NavigationController? if let parentController = selfController.parentController { diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift index 41a5182469..996bfe69ae 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift @@ -267,7 +267,7 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no return } selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, saveInterfaceState: selfController.presentationInterfaceState.subject != .scheduledMessages, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) } }) selfController.openScheduledMessages() } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 6f31174cd7..5f8cb0ab56 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -2056,7 +2056,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) @@ -2118,7 +2118,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var current = current current = current.updatedInterfaceState { interfaceState in var interfaceState = interfaceState - interfaceState = interfaceState.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) + interfaceState = interfaceState.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) if clearInput { interfaceState = interfaceState.withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString())) } @@ -2255,7 +2255,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) }.updatedInputMode { current in + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) }.updatedInputMode { current in if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil { return .media(mode: mode, expanded: nil, focused: focused) } @@ -2305,15 +2305,50 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.enqueueChatContextResult(collection, result, hideVia: true, closeMediaInput: true, silentPosting: silentPosting, resetTextInputState: resetTextInputState) return true - }, requestMessageActionCallback: { [weak self] messageId, data, isGame, requiresPassword in + }, requestMessageActionCallback: { [weak self] message, data, isGame, requiresPassword in guard let strongSelf = self else { return } + + let messageId = message.id + guard strongSelf.presentationInterfaceState.subject != .scheduledMessages else { strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.ScheduledMessages_BotActionUnavailable, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return } + if let attribute = message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute { + guard let data = data?.makeData() else { + return + } + if data.count != 1 { + return + } + let buttonType = data.withUnsafeBytes { buffer -> UInt8 in + return buffer.baseAddress!.assumingMemoryBound(to: UInt8.self).pointee + } + + 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() + 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() + case 2: + //suggest changes + break + default: + break + } + } + + return + } + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId)) |> deliverOnMainQueue).startStandalone(next: { message in guard let strongSelf = self, let message = message else { @@ -2705,7 +2740,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreviews([]) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreviews([]) } }) } }, nil) @@ -3382,7 +3417,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) { [weak self] in if let strongSelf = self { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, saveInterfaceState: strongSelf.presentationInterfaceState.subject != .scheduledMessages, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) } }) if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { @@ -3568,7 +3603,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G completion(nil) } } else { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) }) }, completion: completion) + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) }) }, completion: completion) } } }, displayImportedMessageTooltip: { [weak self] _ in @@ -7058,7 +7093,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } interfaceState = interfaceState.withUpdatedInputLanguage(self.chatDisplayNode.currentTextInputLanguage) /*if case .peer = self.chatLocation, let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum { - interfaceState = interfaceState.withUpdatedComposeInputState(ChatTextInputState()).withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) + interfaceState = interfaceState.withUpdatedComposeInputState(ChatTextInputState()).withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) }*/ let _ = ChatInterfaceState.update(engine: self.context.engine, peerId: peerId, threadId: threadId, { _ in return interfaceState @@ -7925,6 +7960,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G attributes.append(EffectMessageAttribute(id: sendMessageEffect)) } } + if let postSuggestionState = self.presentationInterfaceState.interfaceState.postSuggestionState { + if attributes.first(where: { $0 is SuggestedPostMessageAttribute }) == nil { + attributes.append(SuggestedPostMessageAttribute( + amount: postSuggestionState.price, + timestamp: postSuggestionState.timestamp, + state: nil + )) + } + } return attributes } } @@ -8153,7 +8197,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } completionImpl?() @@ -8199,7 +8243,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if resetTextInputState { state = state.updatedInterfaceState { interfaceState in var interfaceState = interfaceState - interfaceState = interfaceState.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) + interfaceState = interfaceState.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) interfaceState = interfaceState.withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) interfaceState = interfaceState.withUpdatedComposeDisableUrlPreviews([]) return interfaceState diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index b54a9f3fec..7c245439ac 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -4718,7 +4718,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { strongSelf.ignoreUpdateHeight = true textInputPanelNode.text = "" - strongSelf.requestUpdateChatInterfaceState(.immediate, true, { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeDisableUrlPreviews([]) }) + strongSelf.requestUpdateChatInterfaceState(.immediate, true, { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeDisableUrlPreviews([]) }) strongSelf.ignoreUpdateHeight = false } }, usedCorrelationId) diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index c9a0000ce1..949511617f 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -418,7 +418,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) @@ -483,7 +483,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) @@ -560,7 +560,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) @@ -596,7 +596,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) @@ -684,7 +684,7 @@ extension ChatControllerImpl { controller.completion = { [weak self] in if let strongSelf = self { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() } @@ -1180,7 +1180,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) @@ -1633,7 +1633,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) @@ -1683,7 +1683,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) @@ -1749,7 +1749,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) @@ -1774,7 +1774,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.collapseInput() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) @@ -2015,7 +2015,7 @@ extension ChatControllerImpl { self.chatDisplayNode.collapseInput() self.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) @@ -2072,7 +2072,7 @@ extension ChatControllerImpl { self.chatDisplayNode.collapseInput() self.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil) } + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 792cd7860e..4dee7db1c6 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -935,10 +935,10 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { controllerInteraction.shareAccountContact() return case .openWebApp: - controllerInteraction.requestMessageActionCallback(message.id, nil, true, false) + controllerInteraction.requestMessageActionCallback(message, nil, true, false) return case let .callback(requiresPassword, data): - controllerInteraction.requestMessageActionCallback(message.id, data, false, requiresPassword) + controllerInteraction.requestMessageActionCallback(message, data, false, requiresPassword) return case let .switchInline(samePeer, query, peerTypes): var botPeer: Peer? From 76a24167d920965d89973675e2545794ac6f3c98 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 13 Jun 2025 02:22:28 +0800 Subject: [PATCH 2/5] Update API --- .../Sources/State/PaidMessages.swift | 39 +++++++++++++++++++ .../Peers/TelegramEnginePeers.swift | 4 ++ 2 files changed, 43 insertions(+) diff --git a/submodules/TelegramCore/Sources/State/PaidMessages.swift b/submodules/TelegramCore/Sources/State/PaidMessages.swift index a8507fb621..6d74d55185 100644 --- a/submodules/TelegramCore/Sources/State/PaidMessages.swift +++ b/submodules/TelegramCore/Sources/State/PaidMessages.swift @@ -90,6 +90,45 @@ func _internal_addNoPaidMessagesException(account: Account, scopePeerId: PeerId, } } +func _internal_reinstateNoPaidMessagesException(account: Account, scopePeerId: PeerId, peerId: PeerId) -> Signal { + return account.postbox.transaction { transaction -> (Api.InputPeer?, Api.InputUser?) in + return (transaction.getPeer(scopePeerId).flatMap(apiInputPeer), transaction.getPeer(peerId).flatMap(apiInputUser)) + } + |> mapToSignal { scopeInputPeer, inputUser -> Signal in + if scopePeerId != account.peerId { + if scopeInputPeer == nil { + return .never() + } + } else { + return .never() + } + guard let inputUser else { + return .never() + } + var flags: Int32 = 0 + flags |= (1 << 2) + return account.network.request(Api.functions.account.toggleNoPaidMessagesException(flags: flags, parentPeer: scopeInputPeer, userId: inputUser)) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } |> mapToSignal { _ in + return account.postbox.transaction { transaction -> Void in + if scopePeerId != account.peerId { + guard var data = transaction.getMessageHistoryThreadInfo(peerId: scopePeerId, threadId: peerId.toInt64())?.data.get(MessageHistoryThreadData.self) else { + return + } + data.isMessageFeeRemoved = false + + if let entry = StoredMessageHistoryThreadInfo(data) { + transaction.setMessageHistoryThreadInfo(peerId: scopePeerId, threadId: peerId.toInt64(), info: entry) + } + } + } + |> ignoreValues + } + |> ignoreValues + } +} + func _internal_updateChannelPaidMessagesStars(account: Account, peerId: PeerId, stars: StarsAmount?, broadcastMessagesAllowed: Bool) -> Signal { return account.postbox.transaction { transaction -> Signal in guard let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) else { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 2c070b3e45..0c04c5439d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -1502,6 +1502,10 @@ public extension TelegramEngine { return _internal_addNoPaidMessagesException(account: self.account, scopePeerId: scopePeerId, peerId: peerId, refundCharged: refundCharged) } + public func reinstateNoPaidMessagesException(scopePeerId: EnginePeer.Id, peerId: EnginePeer.Id) -> Signal { + return _internal_reinstateNoPaidMessagesException(account: self.account, scopePeerId: scopePeerId, peerId: peerId) + } + public func updateChannelPaidMessagesStars(peerId: EnginePeer.Id, stars: StarsAmount?, broadcastMessagesAllowed: Bool) -> Signal { return _internal_updateChannelPaidMessagesStars(account: self.account, peerId: peerId, stars: stars, broadcastMessagesAllowed: broadcastMessagesAllowed) } From f1b98f6dd8b7a0d924aa1eb3f441590a8bfa0c49 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 13 Jun 2025 17:54:04 +0800 Subject: [PATCH 3/5] 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 From 0a30a0ee563903230bed7c5331f704dea9a335c0 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 13 Jun 2025 20:01:10 +0800 Subject: [PATCH 4/5] Suggested posts --- .../Sources/AccountContext.swift | 1 + .../PromptUI/Sources/PromptController.swift | 9 ++-- .../Messages/TelegramEngineMessages.swift | 31 ++++++++++++ .../Sources/ServiceMessageStrings.swift | 39 +++++++++++---- .../ChatMessageActionBubbleContentNode.swift | 50 +++++++++++++------ .../Sources/ChatMessageBubbleItemNode.swift | 2 +- .../ChatMessageSuggestedPostInfoNode/BUILD | 1 + .../ChatMessageSuggestedPostInfoNode.swift | 18 ++++++- .../Sources/ChatSendStarsScreen.swift | 7 +-- .../Sources/ChatScheduleTimeController.swift | 6 +-- .../ChatScheduleTimeControllerNode.swift | 18 ++++--- .../Sources/StarsWithdrawalScreen.swift | 43 +++++++++++----- ...StoryItemSetContainerViewSendMessage.swift | 5 +- .../TelegramUI/Sources/ChatController.swift | 44 ++++++++++++++-- .../Sources/ChatInterfaceInputContexts.swift | 2 +- .../Sources/SharedAccountContext.swift | 2 + 16 files changed, 208 insertions(+), 70 deletions(-) diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 8dbdc1b27c..4c40bb195f 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1034,6 +1034,7 @@ public enum StarsWithdrawalScreenSubject { case withdraw(completion: (Int64) -> Void) case enterAmount(current: StarsAmount, minValue: StarsAmount, fractionAfterCommission: Int, kind: PaidMessageKind, completion: (Int64) -> Void) case postSuggestion(channel: EnginePeer, current: StarsAmount, timestamp: Int32?, completion: (Int64, Int32?) -> Void) + case postSuggestionModification(current: StarsAmount, timestamp: Int32?, completion: (Int64, Int32?) -> Void) } public protocol SharedAccountContext: AnyObject { diff --git a/submodules/PromptUI/Sources/PromptController.swift b/submodules/PromptUI/Sources/PromptController.swift index 46e58a30ab..06dee32735 100644 --- a/submodules/PromptUI/Sources/PromptController.swift +++ b/submodules/PromptUI/Sources/PromptController.swift @@ -189,7 +189,7 @@ private final class PromptAlertContentNode: AlertContentNode { return self.isUserInteractionEnabled } - init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, titleFont: PromptControllerTitleFont, value: String?, characterLimit: Int) { + init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, titleFont: PromptControllerTitleFont, value: String?, placeholder: String?, characterLimit: Int) { self.strings = strings self.text = text self.titleFont = titleFont @@ -197,7 +197,7 @@ private final class PromptAlertContentNode: AlertContentNode { self.textNode = ASTextNode() self.textNode.maximumNumberOfLines = 2 - self.inputFieldNode = PromptInputFieldNode(theme: ptheme, placeholder: "", characterLimit: characterLimit) + self.inputFieldNode = PromptInputFieldNode(theme: ptheme, placeholder: placeholder ?? "", characterLimit: characterLimit) self.inputFieldNode.text = value ?? "" self.actionNodesSeparator = ASDisplayNode() @@ -407,7 +407,7 @@ public enum PromptControllerTitleFont { case bold } -public func promptController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, text: String, titleFont: PromptControllerTitleFont = .regular, value: String?, characterLimit: Int = 1000, apply: @escaping (String?) -> Void) -> AlertController { +public func promptController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, text: String, titleFont: PromptControllerTitleFont = .regular, value: String?, placeholder: String? = nil, characterLimit: Int = 1000, apply: @escaping (String?) -> Void) -> AlertController { let presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 } var dismissImpl: ((Bool) -> Void)? @@ -421,8 +421,9 @@ public func promptController(sharedContext: SharedAccountContext, updatedPresent applyImpl?() })] - let contentNode = PromptAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, titleFont: titleFont, value: value, characterLimit: characterLimit) + let contentNode = PromptAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, titleFont: titleFont, value: value, placeholder: placeholder, characterLimit: characterLimit) contentNode.complete = { + dismissImpl?(true) applyImpl?() } applyImpl = { [weak contentNode] in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index b1e1a49b2d..6303f27d11 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -1591,6 +1591,7 @@ 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 { @@ -1601,6 +1602,34 @@ 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 @@ -1626,6 +1655,8 @@ 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/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 1272dc5406..029e11e6f1 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1390,18 +1390,35 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } let string: String - switch status { - case .approved: - if messageText.isEmpty { - string = "Your message was approved" - } else { - string = "Your message \"\(messageText)\" was approved" + if !message.flags.contains(.Incoming) { + switch status { + case .approved: + if messageText.isEmpty { + string = "The message was approved" + } else { + string = "The message \"\(messageText)\" was approved" + } + case .rejected: + if messageText.isEmpty { + string = "The message was declined" + } else { + string = "The message \"\(messageText)\" was declined" + } } - case .rejected: - if messageText.isEmpty { - string = "Your message was rejected" - } else { - string = "Your message \"\(messageText)\" was rejected" + } else { + switch status { + case .approved: + if messageText.isEmpty { + string = "Your message was approved" + } else { + string = "Your message \"\(messageText)\" was approved" + } + case .rejected: + if messageText.isEmpty { + string = "Your message was declined" + } else { + string = "Your message \"\(messageText)\" was declined" + } } } attributedString = NSAttributedString(string: string, font: titleFont, textColor: primaryTextColor) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift index dabd04bc95..1862952975 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift @@ -231,22 +231,27 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { //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: []) + return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_Date(value).string.lowercased(), ranges: []) }, tomorrowFormatString: { value in - return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TomorrowAt(value).string, ranges: []) + return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TomorrowAt(value).string.lowercased(), ranges: []) }, todayFormatString: { value in - return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TodayAt(value).string, ranges: []) + return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TodayAt(value).string.lowercased(), ranges: []) }, yesterdayFormatString: { value in - return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TodayAt(value).string, ranges: []) + return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TodayAt(value).string.lowercased(), 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." + let rawString: String + if !item.message.effectivelyIncoming(item.context.account.peerId) { + rawString = "šŸ“… The post will be automatically published on **\(channelName)** **\(timeString)**.\n\nšŸ’° The user have been charged \(amountString).\n\nāŒ› **\(channelName)** will receive the Stars once the post has been live for 24 hours.\n\nšŸ”„ If your remove the post before it has been live for 24 hours, the user's Stars will be refunded." + } else { + 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), @@ -257,15 +262,28 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { )) 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." + if !item.message.effectivelyIncoming(item.context.account.peerId) { + switch reason { + case .generic: + if let comment { + rawString = "You declined the post with the following comment:\n\n" + comment + } else { + rawString = "You declined the post." + } + case .lowBalance: + rawString = "**\(channelName)** was unable to post the message, because the user did not have enough Stars." + } + } else { + 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." } - 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), @@ -344,7 +362,7 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { backgroundSize.height += imageSize.height + 10.0 } - let titleSpacing: CGFloat = 18.0 + let titleSpacing: CGFloat = 14.0 var contentInsets = UIEdgeInsets() var contentOuterInsets = UIEdgeInsets() @@ -353,8 +371,8 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { 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) + contentInsets = UIEdgeInsets(top: 12.0, left: 16.0, bottom: 12.0, right: 16.0) + contentOuterInsets = UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0) backgroundSize.width += contentInsets.left + contentInsets.right backgroundSize.height += contentInsets.top + contentInsets.bottom diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 26ae6cf18f..a47abf5b62 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -2797,7 +2797,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, item.chatLocation.threadId != nil, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = item.message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething), let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil { + } 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(.sendSomething),*/ 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 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/BUILD index f9e2a62062..703390463f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/BUILD @@ -22,6 +22,7 @@ swift_library( "//submodules/WallpaperBackgroundNode", "//submodules/TelegramUI/Components/Chat/ChatMessageItem", "//submodules/TelegramStringFormatting", + "//submodules/Markdown", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift index 0f19af1025..791abdd7e0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift @@ -12,6 +12,7 @@ import AccountContext import WallpaperBackgroundNode import ChatMessageItem import TelegramStringFormatting +import Markdown public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode { private var titleNode: TextNode? @@ -84,10 +85,23 @@ public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode { 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." + if item.message.author is TelegramChannel { + titleText = "**\(item.message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? " ")** suggests a new price,\ntime, and text for your 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 titleAttributedText = parseMarkdownIntoAttributedString(titleText, attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: serviceColor.primaryText), + bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: serviceColor.primaryText), + link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: serviceColor.primaryText), + linkAttribute: { url in + return ("URL", url) + } + )) + + let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedText, 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())) diff --git a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift index c6e0362642..654807dc07 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift @@ -1400,13 +1400,10 @@ private final class ChatSendStarsScreenComponent: Component { guard let environment = self.environment else { return } - guard case let .suggestPost(suggestPostData) = component.initialData.subjectInitialData else { - return - } - let mode: ChatScheduleTimeControllerMode = .suggestPost + let mode: ChatScheduleTimeControllerMode = .suggestPost(needsTime: false) let theme = environment.theme - let controller = ChatScheduleTimeController(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), peerId: suggestPostData.peer.id, mode: mode, style: .default, currentTime: self.currentSuggestPostTimestamp, minimalTime: nil, dismissByTapOutside: true, completion: { [weak self] time in + let controller = ChatScheduleTimeController(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: mode, style: .default, currentTime: self.currentSuggestPostTimestamp, minimalTime: nil, dismissByTapOutside: true, completion: { [weak self] time in guard let self else { return } diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeController.swift b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeController.swift index 5b8b8655f0..61271df7e7 100644 --- a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeController.swift +++ b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeController.swift @@ -11,7 +11,7 @@ import TelegramPresentationData public enum ChatScheduleTimeControllerMode { case scheduledMessages(sendWhenOnlineAvailable: Bool) case reminders - case suggestPost + case suggestPost(needsTime: Bool) } public enum ChatScheduleTimeControllerStyle { @@ -27,7 +27,6 @@ public final class ChatScheduleTimeController: ViewController { private var animatedIn = false private let context: AccountContext - private let peerId: PeerId private let mode: ChatScheduleTimeControllerMode private let style: ChatScheduleTimeControllerStyle private let currentTime: Int32? @@ -40,9 +39,8 @@ public final class ChatScheduleTimeController: ViewController { public var dismissed: () -> Void = {} - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, mode: ChatScheduleTimeControllerMode, style: ChatScheduleTimeControllerStyle, currentTime: Int32? = nil, minimalTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: ChatScheduleTimeControllerMode, style: ChatScheduleTimeControllerStyle, currentTime: Int32? = nil, minimalTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) { self.context = context - self.peerId = peerId self.mode = mode self.style = style self.currentTime = currentTime != scheduleWhenOnlineTimestamp ? currentTime : nil diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift index 5c117b39ca..84ea7d6d69 100644 --- a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift +++ b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift @@ -100,10 +100,16 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel title = self.presentationData.strings.Conversation_ScheduleMessage_Title case .reminders: title = self.presentationData.strings.Conversation_SetReminder_Title - case .suggestPost: - //TODO:localize - title = "Time" - text = "Set the date and time you want\nyour message to be published." + case let .suggestPost(needsTime): + if needsTime { + //TODO:localize + title = "Time" + text = "Set the date and time you want\nthis message to be published." + } else { + //TODO:localize + title = "Time" + text = "Set the date and time you want\nyour message to be published." + } } self.titleNode = ASTextNode() @@ -169,7 +175,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel self.contentContainerNode.addSubnode(self.doneButton) if case .scheduledMessages(true) = self.mode { self.contentContainerNode.addSubnode(self.onlineButton) - } else if case .suggestPost = self.mode { + } else if case let .suggestPost(needsTime) = self.mode, !needsTime { self.contentContainerNode.addSubnode(self.onlineButton) } @@ -425,7 +431,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel var buttonOffset: CGFloat = 0.0 if case .scheduledMessages(true) = self.mode { buttonOffset += 64.0 - } else if case .suggestPost = self.mode { + } else if case let .suggestPost(needsTime) = self.mode, !needsTime { buttonOffset += 64.0 } diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 5e5d6007fe..84952e5109 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -50,7 +50,7 @@ private final class SheetContent: CombinedComponent { return true } - static var body: Body { + static var body: (CombinedComponentContext) -> CGSize { let closeButton = Child(Button.self) let title = Child(Text.self) let amountSection = Child(ListSectionComponent.self) @@ -61,7 +61,7 @@ private final class SheetContent: CombinedComponent { let balanceValue = Child(MultilineTextComponent.self) let balanceIcon = Child(BundleIconComponent.self) - return { context in + return { (context: CombinedComponentContext) -> CGSize in let environment = context.environment[EnvironmentType.self] let component = context.component let state = context.state @@ -160,9 +160,14 @@ private final class SheetContent: CombinedComponent { minAmount = StarsAmount(value: minAmountValue, nanos: 0) maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0) - case .suggestedPost: + case let .suggestedPost(mode, _, _, _): //TODO:localize - titleString = "Suggest Terms" + switch mode { + case .sender: + titleString = "Suggest Terms" + case .admin: + titleString = "Suggest Changes" + } amountTitle = "ENTER A PRICE IN STARS" amountPlaceholder = "Price" @@ -321,6 +326,13 @@ private final class SheetContent: CombinedComponent { text: .plain(amountInfoString), maximumNumberOfLines: 0 )) + case .admin: + //TODO:localize + let amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("Choose how many Stars you charge for the message.", attributes: amountMarkdownAttributes, textAlignment: .natural)) + amountFooter = AnyComponent(MultilineTextComponent( + text: .plain(amountInfoString), + maximumNumberOfLines: 0 + )) } default: amountFooter = nil @@ -380,11 +392,19 @@ private final class SheetContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width - amountAdditionalLabel.size.width / 2.0 - sideInset - 16.0, y: contentSize.height - amountAdditionalLabel.size.height / 2.0))) } - if case .suggestedPost = component.mode { + if case let .suggestedPost(mode, _, _, _) = component.mode { contentSize.height += 24.0 //TODO:localize - let timestampFooterString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("Select the date and time you want your message to be published.", attributes: amountMarkdownAttributes, textAlignment: .natural)) + let footerString: String + switch mode { + case .sender: + footerString = "Select the date and time you want your message to be published." + case .admin: + footerString = "Select the date and time you want to publish the message." + } + + let timestampFooterString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(footerString, attributes: amountMarkdownAttributes, textAlignment: .natural)) let timestampFooter = AnyComponent(MultilineTextComponent( text: .plain(timestampFooterString), maximumNumberOfLines: 0 @@ -443,15 +463,9 @@ private final class SheetContent: CombinedComponent { return } let component = state.component - guard case let .suggestedPost(mode, _, _, _) = component.mode else { - return - } - guard case let .sender(channel) = mode else { - return - } let theme = environment.theme - let controller = ChatScheduleTimeController(context: state.context, updatedPresentationData: (state.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), state.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), peerId: channel.id, mode: .suggestPost, style: .default, currentTime: state.timestamp, minimalTime: nil, dismissByTapOutside: true, completion: { [weak state] time in + let controller = ChatScheduleTimeController(context: state.context, updatedPresentationData: (state.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), state.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: .suggestPost(needsTime: false), style: .default, currentTime: state.timestamp, minimalTime: nil, dismissByTapOutside: true, completion: { [weak state] time in guard let state else { return } @@ -498,6 +512,8 @@ private final class SheetContent: CombinedComponent { } else { buttonString = "Offer" } + case .admin: + buttonString = "Update Terms" } } else if let amount = state.amount { buttonString = "\(environment.strings.Stars_Withdraw_Withdraw) # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))" @@ -770,6 +786,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer { public enum Mode { public enum SuggestedPostMode { case sender(channel: EnginePeer) + case admin } case withdraw(StarsRevenueStats, completion: (Int64) -> Void) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 14f1e8d9f2..4b192ed122 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -466,6 +466,7 @@ final class StoryItemSetContainerSendMessage { guard let peerId = focusedItem.peerId else { return } + let _ = peerId let controller = component.controller() as? StoryContainerScreen var sendWhenOnlineAvailable = false @@ -473,7 +474,7 @@ final class StoryItemSetContainerSendMessage { sendWhenOnlineAvailable = true } - let timeController = ChatScheduleTimeController(context: component.context, updatedPresentationData: nil, peerId: peerId, mode: .scheduledMessages(sendWhenOnlineAvailable: sendWhenOnlineAvailable), style: .media, currentTime: nil, minimalTime: nil, dismissByTapOutside: true, completion: { [weak self, weak view] time in + let timeController = ChatScheduleTimeController(context: component.context, updatedPresentationData: nil, mode: .scheduledMessages(sendWhenOnlineAvailable: sendWhenOnlineAvailable), style: .media, currentTime: nil, minimalTime: nil, dismissByTapOutside: true, completion: { [weak self, weak view] time in guard let self, let view else { return } @@ -2607,7 +2608,7 @@ final class StoryItemSetContainerSendMessage { mode = .scheduledMessages(sendWhenOnlineAvailable: sendWhenOnlineAvailable) } let theme = component.theme - let controller = ChatScheduleTimeController(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), peerId: peer.id, mode: mode, style: style, currentTime: selectedTime, minimalTime: nil, dismissByTapOutside: dismissByTapOutside, completion: { time in + let controller = ChatScheduleTimeController(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: mode, style: style, currentTime: selectedTime, minimalTime: nil, dismissByTapOutside: dismissByTapOutside, completion: { time in completion(time) }) view.endEditing(true) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index b1b10e9cf3..3ca0ebb065 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -137,6 +137,7 @@ import ChatMessagePaymentAlertController import TelegramCallsUI import QuickShareScreen import PostSuggestionsSettingsScreen +import PromptUI public final class ChatControllerOverlayPresentationData { public let expandData: (ASDisplayNode?, () -> Void) @@ -2331,16 +2332,49 @@ 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, action: .reject(comment: nil)).startStandalone() + //TODO:localize + let promptController = promptController(sharedContext: strongSelf.context.sharedContext, updatedPresentationData: strongSelf.updatedPresentationData, text: "Comment", titleFont: .bold, value: "", placeholder: "Optional", characterLimit: 4096, apply: { value in + guard let self else { + return + } + if let value { + let _ = self.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .reject(comment: value.isEmpty ? nil : value)).startStandalone() + } + }) + strongSelf.present(promptController, in: .window(.root)) case 1: var timestamp: Int32? if attribute.timestamp == nil { + let controller = ChatScheduleTimeController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .suggestPost(needsTime: true), style: .default, currentTime: nil, minimalTime: nil, dismissByTapOutside: true, completion: { [weak strongSelf] time in + guard let strongSelf else { + return + } + if time != 0 { + let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: time)).startStandalone() + } + }) + strongSelf.view.endEditing(true) + strongSelf.present(controller, in: .window(.root)) + timestamp = Int32(Date().timeIntervalSince1970) + 1 * 60 * 60 + } else { + let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: timestamp)).startStandalone() } - let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: timestamp)).startStandalone() case 2: - //suggest changes - break + strongSelf.push(strongSelf.context.sharedContext.makeStarsWithdrawalScreen( + context: strongSelf.context, + subject: .postSuggestionModification( + current: StarsAmount(value: attribute.amount, nanos: 0), + timestamp: attribute.timestamp, + completion: { [weak strongSelf] price, timestamp in + guard let strongSelf else { + return + } + + let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .proposeChanges(amount: price, timestamp: timestamp)).startStandalone() + } + ) + )) default: break } @@ -9525,7 +9559,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else { mode = .scheduledMessages(sendWhenOnlineAvailable: sendWhenOnlineAvailable) } - let controller = ChatScheduleTimeController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, mode: mode, style: style, currentTime: selectedTime, minimalTime: strongSelf.presentationInterfaceState.slowmodeState?.timeout, dismissByTapOutside: dismissByTapOutside, completion: { time in + let controller = ChatScheduleTimeController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: mode, style: style, currentTime: selectedTime, minimalTime: strongSelf.presentationInterfaceState.slowmodeState?.timeout, dismissByTapOutside: dismissByTapOutside, completion: { time in completion(time) }) strongSelf.chatDisplayNode.dismissInput() diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift index 7c25066142..fb27b9fbed 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift @@ -233,7 +233,7 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte } } - if isTextEmpty, let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let mainChannel = chatPresentationInterfaceState.renderedPeer?.chatOrMonoforumMainPeer as? TelegramChannel, !mainChannel.hasPermission(.sendSomething) { + if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let mainChannel = chatPresentationInterfaceState.renderedPeer?.chatOrMonoforumMainPeer as? TelegramChannel, !mainChannel.hasPermission(.sendSomething) { if chatPresentationInterfaceState.interfaceState.postSuggestionState == nil { accessoryItems.append(.suggestPost) } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 5ebb3a0069..37b6b1ad6f 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -3731,6 +3731,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mode = .paidMessages(current: current.value, minValue: minValue.value, fractionAfterCommission: fractionAfterCommission, kind: kind, completion: completion) case let .postSuggestion(channel, current, timestamp, completion): mode = .suggestedPost(mode: .sender(channel: channel), price: current.value, timestamp: timestamp, completion: completion) + case let .postSuggestionModification(current, timestamp, completion): + mode = .suggestedPost(mode: .admin, price: current.value, timestamp: timestamp, completion: completion) } return StarsWithdrawScreen(context: context, mode: mode) } From f1efac862b5b960d15cb7d0c8f555fbfca60673b Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 13 Jun 2025 20:42:13 +0800 Subject: [PATCH 5/5] Bump version --- .../Sources/ChatMessageSuggestedPostInfoNode.swift | 6 +++++- versions.json | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift index 791abdd7e0..a7beb77d71 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift @@ -83,7 +83,11 @@ public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode { //TODO:localize let titleText: String if !item.message.effectivelyIncoming(item.context.account.peerId) { - titleText = "You suggest to post\nthis message." + if item.message.attributes.contains(where: { $0 is ReplyMessageAttribute }) { + titleText = "You suggest a new price,\ntime and text for this message." + } else { + titleText = "You suggest to post\nthis 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." diff --git a/versions.json b/versions.json index d861f48abd..492f69fe30 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "11.12.1", + "app": "11.13", "xcode": "16.2", "bazel": "8.2.1:22ff65b05869f6160e5157b1b425a14a62085d71d8baef571f462b8fe5a703a3", "macos": "15"