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

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 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

View File

@ -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<Api.Updates>) {
static func toggleSuggestedPostApproval(flags: Int32, peer: Api.InputPeer, msgId: Int32, scheduleDate: Int32?, rejectComment: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
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() {

View File

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

View File

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

View File

@ -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<Never, NoError> {
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<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 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<Api.Updates?, NoError> in
return .single(nil)

View File

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

View File

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

View File

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

View File

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

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) {
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