Update API [skip ci]

This commit is contained in:
Isaac 2025-06-13 17:54:04 +08:00
parent 76a24167d9
commit f1b98f6dd8
13 changed files with 519 additions and 43 deletions

View File

@ -607,7 +607,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1192749220] = { return Api.MessageAction.parse_messageActionStarGift($0) } dict[1192749220] = { return Api.MessageAction.parse_messageActionStarGift($0) }
dict[775611918] = { return Api.MessageAction.parse_messageActionStarGiftUnique($0) } dict[775611918] = { return Api.MessageAction.parse_messageActionStarGiftUnique($0) }
dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($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[-940721021] = { return Api.MessageAction.parse_messageActionTodoAppendTasks($0) }
dict[-864265079] = { return Api.MessageAction.parse_messageActionTodoCompletions($0) } dict[-864265079] = { return Api.MessageAction.parse_messageActionTodoCompletions($0) }
dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) } dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) }

View File

@ -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 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 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 messageActionSuggestProfilePhoto(photo: Api.Photo)
case messageActionSuggestedPostApproval(flags: Int32) case messageActionSuggestedPostApproval(flags: Int32, rejectComment: String?, scheduleDate: Int32?, starsAmount: Int64?)
case messageActionTodoAppendTasks(list: [Api.TodoItem]) case messageActionTodoAppendTasks(list: [Api.TodoItem])
case messageActionTodoCompletions(completed: [Int32], incompleted: [Int32]) case messageActionTodoCompletions(completed: [Int32], incompleted: [Int32])
case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?)
@ -804,11 +804,14 @@ public extension Api {
} }
photo.serialize(buffer, true) photo.serialize(buffer, true)
break break
case .messageActionSuggestedPostApproval(let flags): case .messageActionSuggestedPostApproval(let flags, let rejectComment, let scheduleDate, let starsAmount):
if boxed { if boxed {
buffer.appendInt32(866770590) buffer.appendInt32(-1354584535)
} }
serializeInt32(flags, buffer: buffer, boxed: false) 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 break
case .messageActionTodoAppendTasks(let list): case .messageActionTodoAppendTasks(let list):
if boxed { 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)]) 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): case .messageActionSuggestProfilePhoto(let photo):
return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)]) return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)])
case .messageActionSuggestedPostApproval(let flags): case .messageActionSuggestedPostApproval(let flags, let rejectComment, let scheduleDate, let starsAmount):
return ("messageActionSuggestedPostApproval", [("flags", flags as Any)]) return ("messageActionSuggestedPostApproval", [("flags", flags as Any), ("rejectComment", rejectComment as Any), ("scheduleDate", scheduleDate as Any), ("starsAmount", starsAmount as Any)])
case .messageActionTodoAppendTasks(let list): case .messageActionTodoAppendTasks(let list):
return ("messageActionTodoAppendTasks", [("list", list as Any)]) return ("messageActionTodoAppendTasks", [("list", list as Any)])
case .messageActionTodoCompletions(let completed, let incompleted): case .messageActionTodoCompletions(let completed, let incompleted):
@ -1770,9 +1773,18 @@ public extension Api {
public static func parse_messageActionSuggestedPostApproval(_ reader: BufferReader) -> MessageAction? { public static func parse_messageActionSuggestedPostApproval(_ reader: BufferReader) -> MessageAction? {
var _1: Int32? var _1: Int32?
_1 = reader.readInt32() _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 let _c1 = _1 != nil
if _c1 { let _c2 = (Int(_1!) & Int(1 << 2) == 0) || _2 != nil
return Api.MessageAction.messageActionSuggestedPostApproval(flags: _1!) 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 { else {
return nil return nil

View File

@ -8909,14 +8909,15 @@ public extension Api.functions.messages {
} }
} }
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<Api.Updates>) { static func toggleSuggestedPostApproval(flags: Int32, peer: Api.InputPeer, msgId: Int32, scheduleDate: Int32?, rejectComment: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(806598987) buffer.appendInt32(-2130229924)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true) peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false) serializeInt32(msgId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(scheduleDate!, 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) let reader = BufferReader(buffer)
var result: Api.Updates? var result: Api.Updates?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {

View File

@ -228,7 +228,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return TelegramMediaAction(action: .todoCompletions(completed: completed, incompleted: incompleted)) return TelegramMediaAction(action: .todoCompletions(completed: completed, incompleted: incompleted))
case let .messageActionTodoAppendTasks(list): case let .messageActionTodoAppendTasks(list):
return TelegramMediaAction(action: .todoAppendTasks(list.map { TelegramMediaTodo.Item(apiItem: $0) })) 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 let status: TelegramMediaActionType.SuggestedPostApprovalStatus
if (flags & (1 << 0)) != 0 { if (flags & (1 << 0)) != 0 {
let reason: TelegramMediaActionType.SuggestedPostApprovalStatus.RejectionReason let reason: TelegramMediaActionType.SuggestedPostApprovalStatus.RejectionReason
@ -237,9 +237,9 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
} else { } else {
reason = .generic reason = .generic
} }
status = .rejected(reason: reason) status = .rejected(reason: reason, comment: rejectComment)
} else { } else {
status = .approved status = .approved(timestamp: scheduleDate, amount: starsAmount ?? 0)
} }
return TelegramMediaAction(action: .suggestedPostApprovalStatus(status: status)) return TelegramMediaAction(action: .suggestedPostApprovalStatus(status: status))
} }

View File

@ -117,13 +117,16 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case lowBalance case lowBalance
} }
case approved case approved(timestamp: Int32?, amount: Int64)
case rejected(reason: RejectionReason) case rejected(reason: RejectionReason, comment: String?)
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("_t", orElse: 0) { switch decoder.decodeInt32ForKey("_t", orElse: 0) {
case 0: case 0:
self = .approved self = .approved(
timestamp: decoder.decodeOptionalInt32ForKey("ts"),
amount: decoder.decodeInt64ForKey("am", orElse: 0)
)
case 1: case 1:
let reason: RejectionReason let reason: RejectionReason
switch decoder.decodeInt32ForKey("rs", orElse: 0) { switch decoder.decodeInt32ForKey("rs", orElse: 0) {
@ -135,18 +138,24 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
assertionFailure() assertionFailure()
reason = .generic reason = .generic
} }
self = .rejected(reason: reason) self = .rejected(reason: reason, comment: decoder.decodeOptionalStringForKey("com"))
default: default:
assertionFailure() assertionFailure()
self = .rejected(reason: .generic) self = .rejected(reason: .generic, comment: nil)
} }
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
switch self { switch self {
case .approved: case let .approved(timestamp, amount):
encoder.encodeInt32(0, forKey: "_t") 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") encoder.encodeInt32(1, forKey: "_t")
switch reason { switch reason {
case .generic: case .generic:
@ -154,6 +163,11 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case .lowBalance: case .lowBalance:
encoder.encodeInt32(1, forKey: "rs") 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: case 51:
let status: SuggestedPostApprovalStatus? = decoder.decodeObjectForKey("st", decoder: { SuggestedPostApprovalStatus(decoder: $0) }) as? SuggestedPostApprovalStatus 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: default:
self = .unknown self = .unknown
} }

View File

@ -1588,13 +1588,18 @@ public extension TelegramEngine {
return _internal_requestMessageAuthor(account: self.account, id: id) return _internal_requestMessageAuthor(account: self.account, id: id)
} }
public func monoforumPerformSuggestedPostAction(id: EngineMessage.Id, approve: Bool, timestamp: Int32?) -> Signal<Never, NoError> { public enum MonoforumSuggestedPostAction {
return _internal_monoforumPerformSuggestedPostAction(account: self.account, id: id, approve: approve, timestamp: timestamp) case approve(timestamp: Int32?)
case reject(comment: String?)
}
public func monoforumPerformSuggestedPostAction(id: EngineMessage.Id, action: MonoforumSuggestedPostAction) -> Signal<Never, NoError> {
return _internal_monoforumPerformSuggestedPostAction(account: self.account, id: id, action: action)
} }
} }
} }
func _internal_monoforumPerformSuggestedPostAction(account: Account, id: EngineMessage.Id, approve: Bool, timestamp: Int32?) -> Signal<Never, NoError> { func _internal_monoforumPerformSuggestedPostAction(account: Account, id: EngineMessage.Id, action: TelegramEngine.Messages.MonoforumSuggestedPostAction) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Api.InputPeer? in return account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(id.peerId).flatMap(apiInputPeer) return transaction.getPeer(id.peerId).flatMap(apiInputPeer)
} }
@ -1607,13 +1612,22 @@ func _internal_monoforumPerformSuggestedPostAction(account: Account, id: EngineM
} }
var flags: Int32 = 0 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 flags |= 1 << 1
rejectComment = commentValue
if rejectComment != nil {
flags |= 1 << 2
}
} }
if timestamp != nil { return account.network.request(Api.functions.messages.toggleSuggestedPostApproval(flags: flags, peer: inputPeer, msgId: id.id, scheduleDate: timestamp, rejectComment: rejectComment))
flags |= 1 << 0
}
return account.network.request(Api.functions.messages.toggleSuggestedPostApproval(flags: flags, peer: inputPeer, msgId: id.id, scheduleDate: timestamp))
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in |> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil) return .single(nil)

View File

@ -31,6 +31,7 @@ swift_library(
"//submodules/TelegramUI/Components/TextNodeWithEntities", "//submodules/TelegramUI/Components/TextNodeWithEntities",
"//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode",
"//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon",
"//submodules/Markdown",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -21,6 +21,7 @@ import InvisibleInkDustNode
import TextNodeWithEntities import TextNodeWithEntities
import ChatMessageBubbleContentNode import ChatMessageBubbleContentNode
import ChatMessageItemCommon 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? { 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) 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 class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
public var expandHighlightingNode: LinkHighlightingNode? public var expandHighlightingNode: LinkHighlightingNode?
public var titleNode: TextNode?
public let labelNode: TextNodeWithEntities public let labelNode: TextNodeWithEntities
private var dustNode: InvisibleInkDustNode? private var dustNode: InvisibleInkDustNode?
public var backgroundNode: WallpaperBubbleBackgroundNode? 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))) { 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 makeLabelLayout = TextNodeWithEntities.asyncLayout(self.labelNode)
let cachedMaskBackgroundImage = self.cachedMaskBackgroundImage let cachedMaskBackgroundImage = self.cachedMaskBackgroundImage
@ -184,11 +187,14 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
var image: TelegramMediaImage? var image: TelegramMediaImage?
var story: TelegramMediaStory? var story: TelegramMediaStory?
var suggestedPost: TelegramMediaActionType.SuggestedPostApprovalStatus?
for media in item.message.media { for media in item.message.media {
if let action = media as? TelegramMediaAction { if let action = media as? TelegramMediaAction {
switch action.action { switch action.action {
case let .photoUpdated(img): case let .photoUpdated(img):
image = img image = img
case let .suggestedPostApprovalStatus(status):
suggestedPost = status
default: default:
break break
} }
@ -206,7 +212,94 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
updatedAttributedString = mutableString 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() var labelRects = labelLayout.linesRects()
if labelRects.count > 1 { if labelRects.count > 1 {
@ -231,20 +324,47 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
let backgroundMaskImage: (CGPoint, UIImage)? let backgroundMaskImage: (CGPoint, UIImage)?
var backgroundMaskUpdated = false var backgroundMaskUpdated = false
if let (currentOffset, currentImage, currentRects) = cachedMaskBackgroundImage, currentRects == labelRects { if suggestedPost != nil {
backgroundMaskImage = (currentOffset, currentImage) backgroundMaskImage = nil
if cachedMaskBackgroundImage != nil {
backgroundMaskUpdated = true
}
} else { } else {
backgroundMaskImage = LinkHighlightingNode.generateImage(color: .white, inset: 0.0, innerRadius: 10.0, outerRadius: 10.0, rects: labelRects, useModernPathCalculation: false) if let (currentOffset, currentImage, currentRects) = cachedMaskBackgroundImage, currentRects == labelRects {
backgroundMaskUpdated = true 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 { if let _ = image {
backgroundSize.width = imageSize.width + 2.0 backgroundSize.width = imageSize.width + 2.0
backgroundSize.height += imageSize.height + 10.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 (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 { if let strongSelf = self {
strongSelf.item = item strongSelf.item = item
@ -330,7 +450,31 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
attemptSynchronous: synchronousLoads 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 { if story != nil {
let leadingIconView: UIImageView let leadingIconView: UIImageView
@ -395,7 +539,59 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.expandHighlightingNode = nil 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 item.context.sharedContext.energyUsageSettings.fullTranslucency {
if strongSelf.backgroundNode == nil { if strongSelf.backgroundNode == nil {
if let backgroundNode = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { if let backgroundNode = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
@ -415,7 +611,7 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
if let backgroundNode = strongSelf.backgroundNode { if let backgroundNode = strongSelf.backgroundNode {
if labelRects.count == 1 { if labelRects.count == 1 {
backgroundNode.clipsToBounds = true backgroundNode.clipsToBounds = true
backgroundNode.cornerRadius = labelRects[0].height / 2.0 backgroundNode.cornerRadius = min(32.0, labelRects[0].height / 2.0)
backgroundNode.view.mask = nil backgroundNode.view.mask = nil
} else { } else {
backgroundNode.clipsToBounds = false backgroundNode.clipsToBounds = false

View File

@ -91,6 +91,7 @@ swift_library(
"//submodules/TelegramUI/Components/LottieMetal", "//submodules/TelegramUI/Components/LottieMetal",
"//submodules/TelegramStringFormatting", "//submodules/TelegramStringFormatting",
"//submodules/AvatarNode", "//submodules/AvatarNode",
"//submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -80,6 +80,7 @@ import AnimatedStickerNode
import TelegramAnimatedStickerNode import TelegramAnimatedStickerNode
import LottieMetal import LottieMetal
import AvatarNode import AvatarNode
import ChatMessageSuggestedPostInfoNode
private struct BubbleItemAttributes { private struct BubbleItemAttributes {
var index: Int? var index: Int?
@ -625,6 +626,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
private let shadowNode: ChatMessageShadowNode private let shadowNode: ChatMessageShadowNode
private var clippingNode: ChatMessageBubbleClippingNode private var clippingNode: ChatMessageBubbleClippingNode
private var suggestedPostInfoNode: ChatMessageSuggestedPostInfoNode?
override public var extractedBackgroundNode: ASDisplayNode? { override public var extractedBackgroundNode: ASDisplayNode? {
return self.shadowNode return self.shadowNode
} }
@ -1432,6 +1435,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
let weakSelf = Weak(self) let weakSelf = Weak(self)
let makeSuggestedPostInfoNodeLayout: ChatMessageSuggestedPostInfoNode.AsyncLayout = ChatMessageSuggestedPostInfoNode.asyncLayout(self.suggestedPostInfoNode)
return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in
let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData) let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
return ChatMessageBubbleItemNode.beginLayout( return ChatMessageBubbleItemNode.beginLayout(
@ -1454,6 +1459,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
unlockButtonLayout: unlockButtonLayout, unlockButtonLayout: unlockButtonLayout,
mediaInfoLayout: mediaInfoLayout, mediaInfoLayout: mediaInfoLayout,
mosaicStatusLayout: mosaicStatusLayout, mosaicStatusLayout: mosaicStatusLayout,
makeSuggestedPostInfoNodeLayout: makeSuggestedPostInfoNodeLayout,
layoutConstants: layoutConstants, layoutConstants: layoutConstants,
currentItem: currentItem, currentItem: currentItem,
currentForwardInfo: currentForwardInfo, currentForwardInfo: currentForwardInfo,
@ -1482,6 +1488,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
unlockButtonLayout: (ChatMessageUnlockMediaNode.Arguments) -> (CGSize, (Bool) -> ChatMessageUnlockMediaNode), unlockButtonLayout: (ChatMessageUnlockMediaNode.Arguments) -> (CGSize, (Bool) -> ChatMessageUnlockMediaNode),
mediaInfoLayout: (ChatMessageStarsMediaInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageStarsMediaInfoNode), mediaInfoLayout: (ChatMessageStarsMediaInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageStarsMediaInfoNode),
mosaicStatusLayout: (ChatMessageDateAndStatusNode.Arguments) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode)), mosaicStatusLayout: (ChatMessageDateAndStatusNode.Arguments) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode)),
makeSuggestedPostInfoNodeLayout: ChatMessageSuggestedPostInfoNode.AsyncLayout,
layoutConstants: ChatMessageItemLayoutConstants, layoutConstants: ChatMessageItemLayoutConstants,
currentItem: ChatMessageItem?, currentItem: ChatMessageItem?,
currentForwardInfo: (Peer?, String?)?, currentForwardInfo: (Peer?, String?)?,
@ -3039,7 +3046,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
var totalContentNodesHeight: CGFloat = 0.0 var totalContentNodesHeight: CGFloat = 0.0
var currentContainerGroupOverlap: CGFloat = 0.0 var currentContainerGroupOverlap: CGFloat = 0.0
var detachedContentNodesHeight: CGFloat = 0.0 var detachedContentNodesHeight: CGFloat = 0.0
let additionalTopHeight: CGFloat = 0.0 var additionalTopHeight: CGFloat = 0.0
var mosaicStatusOrigin: CGPoint? var mosaicStatusOrigin: CGPoint?
var unlockButtonPosition: CGPoint? var unlockButtonPosition: CGPoint?
@ -3216,6 +3223,18 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
reactionButtonsSizeAndApply = reactionButtonsFinalize(maxContentWidth) 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 let minimalContentSize: CGSize
if hideBackground { if hideBackground {
minimalContentSize = CGSize(width: 1.0, height: 1.0) minimalContentSize = CGSize(width: 1.0, height: 1.0)
@ -3352,6 +3371,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
avatarOffset: avatarOffset, avatarOffset: avatarOffset,
hidesHeaders: hidesHeaders, hidesHeaders: hidesHeaders,
disablesComments: disablesComments, disablesComments: disablesComments,
suggestedPostInfoNodeLayout: suggestedPostInfoNodeLayout,
alignment: alignment, alignment: alignment,
isSidePanelOpen: isSidePanelOpen isSidePanelOpen: isSidePanelOpen
) )
@ -3415,6 +3435,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
avatarOffset: CGFloat?, avatarOffset: CGFloat?,
hidesHeaders: Bool, hidesHeaders: Bool,
disablesComments: Bool, disablesComments: Bool,
suggestedPostInfoNodeLayout: (CGSize, () -> ChatMessageSuggestedPostInfoNode)?,
alignment: ChatMessageBubbleContentAlignment, alignment: ChatMessageBubbleContentAlignment,
isSidePanelOpen: Bool isSidePanelOpen: Bool
) -> Void { ) -> Void {
@ -3499,6 +3520,22 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
let previousBackgroundFrame = strongSelf.backgroundNode.backgroundFrame let previousBackgroundFrame = strongSelf.backgroundNode.backgroundFrame
strongSelf.backgroundNode.backgroundFrame = 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 { if let avatarOffset {
strongSelf.updateAttachedAvatarNodeOffset(offset: avatarOffset, transition: .animated(duration: 0.3, curve: .spring)) strongSelf.updateAttachedAvatarNodeOffset(offset: avatarOffset, transition: .animated(duration: 0.3, curve: .spring))
} }

View File

@ -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",
],
)

View File

@ -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
})
}
}
}

View File

@ -2331,13 +2331,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if message.effectivelyIncoming(strongSelf.context.account.peerId) { if message.effectivelyIncoming(strongSelf.context.account.peerId) {
switch buttonType { switch buttonType {
case 0: 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: case 1:
var timestamp: Int32? var timestamp: Int32?
if attribute.timestamp == nil { if attribute.timestamp == nil {
timestamp = Int32(Date().timeIntervalSince1970) + 1 * 60 * 60 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: case 2:
//suggest changes //suggest changes
break break