Suggested posts

This commit is contained in:
Isaac 2025-06-13 20:01:10 +08:00
parent f1b98f6dd8
commit 0a30a0ee56
16 changed files with 208 additions and 70 deletions

View File

@ -1034,6 +1034,7 @@ public enum StarsWithdrawalScreenSubject {
case withdraw(completion: (Int64) -> Void)
case enterAmount(current: StarsAmount, minValue: StarsAmount, fractionAfterCommission: Int, kind: PaidMessageKind, completion: (Int64) -> Void)
case postSuggestion(channel: EnginePeer, current: StarsAmount, timestamp: Int32?, completion: (Int64, Int32?) -> Void)
case postSuggestionModification(current: StarsAmount, timestamp: Int32?, completion: (Int64, Int32?) -> Void)
}
public protocol SharedAccountContext: AnyObject {

View File

@ -189,7 +189,7 @@ private final class PromptAlertContentNode: AlertContentNode {
return self.isUserInteractionEnabled
}
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, titleFont: PromptControllerTitleFont, value: String?, characterLimit: Int) {
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, titleFont: PromptControllerTitleFont, value: String?, placeholder: String?, characterLimit: Int) {
self.strings = strings
self.text = text
self.titleFont = titleFont
@ -197,7 +197,7 @@ private final class PromptAlertContentNode: AlertContentNode {
self.textNode = ASTextNode()
self.textNode.maximumNumberOfLines = 2
self.inputFieldNode = PromptInputFieldNode(theme: ptheme, placeholder: "", characterLimit: characterLimit)
self.inputFieldNode = PromptInputFieldNode(theme: ptheme, placeholder: placeholder ?? "", characterLimit: characterLimit)
self.inputFieldNode.text = value ?? ""
self.actionNodesSeparator = ASDisplayNode()
@ -407,7 +407,7 @@ public enum PromptControllerTitleFont {
case bold
}
public func promptController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, text: String, titleFont: PromptControllerTitleFont = .regular, value: String?, characterLimit: Int = 1000, apply: @escaping (String?) -> Void) -> AlertController {
public func promptController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, text: String, titleFont: PromptControllerTitleFont = .regular, value: String?, placeholder: String? = nil, characterLimit: Int = 1000, apply: @escaping (String?) -> Void) -> AlertController {
let presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 }
var dismissImpl: ((Bool) -> Void)?
@ -421,8 +421,9 @@ public func promptController(sharedContext: SharedAccountContext, updatedPresent
applyImpl?()
})]
let contentNode = PromptAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, titleFont: titleFont, value: value, characterLimit: characterLimit)
let contentNode = PromptAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, titleFont: titleFont, value: value, placeholder: placeholder, characterLimit: characterLimit)
contentNode.complete = {
dismissImpl?(true)
applyImpl?()
}
applyImpl = { [weak contentNode] in

View File

@ -1591,6 +1591,7 @@ public extension TelegramEngine {
public enum MonoforumSuggestedPostAction {
case approve(timestamp: Int32?)
case reject(comment: String?)
case proposeChanges(amount: Int64, timestamp: Int32?)
}
public func monoforumPerformSuggestedPostAction(id: EngineMessage.Id, action: MonoforumSuggestedPostAction) -> Signal<Never, NoError> {
@ -1601,6 +1602,34 @@ public extension TelegramEngine {
func _internal_monoforumPerformSuggestedPostAction(account: Account, id: EngineMessage.Id, action: TelegramEngine.Messages.MonoforumSuggestedPostAction) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Api.InputPeer? in
if case let .proposeChanges(amount, timestamp) = action, let message = transaction.getMessage(id) {
var attributes: [MessageAttribute] = []
attributes.append(SuggestedPostMessageAttribute(
amount: amount,
timestamp: timestamp,
state: nil
))
var mediaReference: AnyMediaReference?
if let media = message.media.first {
mediaReference = .message(message: MessageReference(message), media: media)
}
let enqueueMessage: EnqueueMessage = .message(
text: message.text,
attributes: attributes,
inlineStickers: [:],
mediaReference: mediaReference,
threadId: message.threadId,
replyToMessageId: EngineMessageReplySubject(messageId: message.id, quote: nil),
replyToStoryId: nil,
localGroupingKey: nil,
correlationId: nil,
bubbleUpEmojiOrStickersets: []
)
let _ = enqueueMessages(transaction: transaction, account: account, peerId: id.peerId, messages: [(true, enqueueMessage)])
}
return transaction.getPeer(id.peerId).flatMap(apiInputPeer)
}
|> mapToSignal { inputPeer -> Signal<Never, NoError> in
@ -1626,6 +1655,8 @@ func _internal_monoforumPerformSuggestedPostAction(account: Account, id: EngineM
if rejectComment != nil {
flags |= 1 << 2
}
case .proposeChanges:
flags |= 1 << 1
}
return account.network.request(Api.functions.messages.toggleSuggestedPostApproval(flags: flags, peer: inputPeer, msgId: id.id, scheduleDate: timestamp, rejectComment: rejectComment))
|> map(Optional.init)

View File

@ -1390,18 +1390,35 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
}
let string: String
switch status {
case .approved:
if messageText.isEmpty {
string = "Your message was approved"
} else {
string = "Your message \"\(messageText)\" was approved"
if !message.flags.contains(.Incoming) {
switch status {
case .approved:
if messageText.isEmpty {
string = "The message was approved"
} else {
string = "The message \"\(messageText)\" was approved"
}
case .rejected:
if messageText.isEmpty {
string = "The message was declined"
} else {
string = "The message \"\(messageText)\" was declined"
}
}
case .rejected:
if messageText.isEmpty {
string = "Your message was rejected"
} else {
string = "Your message \"\(messageText)\" was rejected"
} else {
switch status {
case .approved:
if messageText.isEmpty {
string = "Your message was approved"
} else {
string = "Your message \"\(messageText)\" was approved"
}
case .rejected:
if messageText.isEmpty {
string = "Your message was declined"
} else {
string = "Your message \"\(messageText)\" was declined"
}
}
}
attributedString = NSAttributedString(string: string, font: titleFont, textColor: primaryTextColor)

View File

@ -231,22 +231,27 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
//TODO:localize
let timeString = humanReadableStringForTimestamp(strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat, timestamp: timestamp ?? 0, alwaysShowTime: true, allowYesterday: false, format: HumanReadableStringFormat(
dateFormatString: { value in
return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_Date(value).string, ranges: [])
return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_Date(value).string.lowercased(), ranges: [])
},
tomorrowFormatString: { value in
return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TomorrowAt(value).string, ranges: [])
return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TomorrowAt(value).string.lowercased(), ranges: [])
},
todayFormatString: { value in
return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TodayAt(value).string, ranges: [])
return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TodayAt(value).string.lowercased(), ranges: [])
},
yesterdayFormatString: { value in
return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TodayAt(value).string, ranges: [])
return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TodayAt(value).string.lowercased(), ranges: [])
}
)).string
let amountString = amount == 1 ? "\(amount) Star" : "\(amount) Stars"
let rawString = "📅 Your post will be automatically published on **\(channelName)** **\(timeString)**.\n\n💰 You have been charged \(amountString).\n\n⌛ **\(channelName)** will receive your Stars once the post has been live for 24 hours.\n\n🔄 If **\(channelName)** removes the post before it has been live for 24 hours, your Stars will be refunded."
let rawString: String
if !item.message.effectivelyIncoming(item.context.account.peerId) {
rawString = "📅 The post will be automatically published on **\(channelName)** **\(timeString)**.\n\n💰 The user have been charged \(amountString).\n\n⌛ **\(channelName)** will receive the Stars once the post has been live for 24 hours.\n\n🔄 If your remove the post before it has been live for 24 hours, the user's Stars will be refunded."
} else {
rawString = "📅 Your post will be automatically published on **\(channelName)** **\(timeString)**.\n\n💰 You have been charged \(amountString).\n\n⌛ **\(channelName)** will receive your Stars once the post has been live for 24 hours.\n\n🔄 If **\(channelName)** removes the post before it has been live for 24 hours, your Stars will be refunded."
}
updatedAttributedString = parseMarkdownIntoAttributedString(rawString, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor),
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: primaryTextColor),
@ -257,15 +262,28 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
))
case let .rejected(reason, comment):
let rawString: String
switch reason {
case .generic:
if let comment {
rawString = "**\(channelName)** declined your post with the following comment:\n\n" + comment
} else {
rawString = "**\(channelName)** declined your post."
if !item.message.effectivelyIncoming(item.context.account.peerId) {
switch reason {
case .generic:
if let comment {
rawString = "You declined the post with the following comment:\n\n" + comment
} else {
rawString = "You declined the post."
}
case .lowBalance:
rawString = "**\(channelName)** was unable to post the message, because the user did not have enough Stars."
}
} else {
switch reason {
case .generic:
if let comment {
rawString = "**\(channelName)** declined your post with the following comment:\n\n" + comment
} else {
rawString = "**\(channelName)** declined your post."
}
case .lowBalance:
rawString = "**\(channelName)** was unable to post your message, because you did not have enough Stars."
}
case .lowBalance:
rawString = "**\(channelName)** was unable to post your message, because you did not have enough Stars."
}
updatedAttributedString = parseMarkdownIntoAttributedString(rawString, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor),
@ -344,7 +362,7 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
backgroundSize.height += imageSize.height + 10.0
}
let titleSpacing: CGFloat = 18.0
let titleSpacing: CGFloat = 14.0
var contentInsets = UIEdgeInsets()
var contentOuterInsets = UIEdgeInsets()
@ -353,8 +371,8 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
backgroundSize.width = max(backgroundSize.width, titleLayoutAndApply.0.size.width)
backgroundSize.height += titleSpacing + titleLayoutAndApply.0.size.height
contentInsets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0)
contentOuterInsets = UIEdgeInsets(top: 8.0, left: 0.0, bottom: 8.0, right: 0.0)
contentInsets = UIEdgeInsets(top: 12.0, left: 16.0, bottom: 12.0, right: 16.0)
contentOuterInsets = UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0)
backgroundSize.width += contentInsets.left + contentInsets.right
backgroundSize.height += contentInsets.top + contentInsets.bottom

View File

@ -2797,7 +2797,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
actionButtonsFinalize = buttonsLayout
lastNodeTopPosition = .None(.Both)
} else if incoming, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.isMonoForum, item.chatLocation.threadId != nil, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = item.message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething), let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil {
} else if incoming, /*let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = item.message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething),*/ let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil {
//TODO:localize
var buttonDecline: UInt8 = 0
var buttonApprove: UInt8 = 1

View File

@ -22,6 +22,7 @@ swift_library(
"//submodules/WallpaperBackgroundNode",
"//submodules/TelegramUI/Components/Chat/ChatMessageItem",
"//submodules/TelegramStringFormatting",
"//submodules/Markdown",
],
visibility = [
"//visibility:public",

View File

@ -12,6 +12,7 @@ import AccountContext
import WallpaperBackgroundNode
import ChatMessageItem
import TelegramStringFormatting
import Markdown
public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode {
private var titleNode: TextNode?
@ -84,10 +85,23 @@ public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode {
if !item.message.effectivelyIncoming(item.context.account.peerId) {
titleText = "You suggest to post\nthis message."
} else {
titleText = "\(item.message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? " ") suggests to post\nthis message."
if item.message.author is TelegramChannel {
titleText = "**\(item.message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? " ")** suggests a new price,\ntime, and text for your message."
} else {
titleText = "**\(item.message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? " ")** suggests to post\nthis message."
}
}
let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleText, font: Font.regular(13.0), textColor: serviceColor.primaryText), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let titleAttributedText = parseMarkdownIntoAttributedString(titleText, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: serviceColor.primaryText),
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: serviceColor.primaryText),
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: serviceColor.primaryText),
linkAttribute: { url in
return ("URL", url)
}
))
let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let priceLabelLayout = makePriceLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Price", font: Font.regular(13.0), textColor: serviceColor.primaryText.withMultipliedAlpha(0.5)), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let timeLabelLayout = makeTimeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Time", font: Font.regular(13.0), textColor: serviceColor.primaryText.withMultipliedAlpha(0.5)), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))

View File

@ -1400,13 +1400,10 @@ private final class ChatSendStarsScreenComponent: Component {
guard let environment = self.environment else {
return
}
guard case let .suggestPost(suggestPostData) = component.initialData.subjectInitialData else {
return
}
let mode: ChatScheduleTimeControllerMode = .suggestPost
let mode: ChatScheduleTimeControllerMode = .suggestPost(needsTime: false)
let theme = environment.theme
let controller = ChatScheduleTimeController(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), peerId: suggestPostData.peer.id, mode: mode, style: .default, currentTime: self.currentSuggestPostTimestamp, minimalTime: nil, dismissByTapOutside: true, completion: { [weak self] time in
let controller = ChatScheduleTimeController(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: mode, style: .default, currentTime: self.currentSuggestPostTimestamp, minimalTime: nil, dismissByTapOutside: true, completion: { [weak self] time in
guard let self else {
return
}

View File

@ -11,7 +11,7 @@ import TelegramPresentationData
public enum ChatScheduleTimeControllerMode {
case scheduledMessages(sendWhenOnlineAvailable: Bool)
case reminders
case suggestPost
case suggestPost(needsTime: Bool)
}
public enum ChatScheduleTimeControllerStyle {
@ -27,7 +27,6 @@ public final class ChatScheduleTimeController: ViewController {
private var animatedIn = false
private let context: AccountContext
private let peerId: PeerId
private let mode: ChatScheduleTimeControllerMode
private let style: ChatScheduleTimeControllerStyle
private let currentTime: Int32?
@ -40,9 +39,8 @@ public final class ChatScheduleTimeController: ViewController {
public var dismissed: () -> Void = {}
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, mode: ChatScheduleTimeControllerMode, style: ChatScheduleTimeControllerStyle, currentTime: Int32? = nil, minimalTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) {
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: ChatScheduleTimeControllerMode, style: ChatScheduleTimeControllerStyle, currentTime: Int32? = nil, minimalTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) {
self.context = context
self.peerId = peerId
self.mode = mode
self.style = style
self.currentTime = currentTime != scheduleWhenOnlineTimestamp ? currentTime : nil

View File

@ -100,10 +100,16 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
title = self.presentationData.strings.Conversation_ScheduleMessage_Title
case .reminders:
title = self.presentationData.strings.Conversation_SetReminder_Title
case .suggestPost:
//TODO:localize
title = "Time"
text = "Set the date and time you want\nyour message to be published."
case let .suggestPost(needsTime):
if needsTime {
//TODO:localize
title = "Time"
text = "Set the date and time you want\nthis message to be published."
} else {
//TODO:localize
title = "Time"
text = "Set the date and time you want\nyour message to be published."
}
}
self.titleNode = ASTextNode()
@ -169,7 +175,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
self.contentContainerNode.addSubnode(self.doneButton)
if case .scheduledMessages(true) = self.mode {
self.contentContainerNode.addSubnode(self.onlineButton)
} else if case .suggestPost = self.mode {
} else if case let .suggestPost(needsTime) = self.mode, !needsTime {
self.contentContainerNode.addSubnode(self.onlineButton)
}
@ -425,7 +431,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
var buttonOffset: CGFloat = 0.0
if case .scheduledMessages(true) = self.mode {
buttonOffset += 64.0
} else if case .suggestPost = self.mode {
} else if case let .suggestPost(needsTime) = self.mode, !needsTime {
buttonOffset += 64.0
}

View File

@ -50,7 +50,7 @@ private final class SheetContent: CombinedComponent {
return true
}
static var body: Body {
static var body: (CombinedComponentContext<SheetContent>) -> CGSize {
let closeButton = Child(Button.self)
let title = Child(Text.self)
let amountSection = Child(ListSectionComponent.self)
@ -61,7 +61,7 @@ private final class SheetContent: CombinedComponent {
let balanceValue = Child(MultilineTextComponent.self)
let balanceIcon = Child(BundleIconComponent.self)
return { context in
return { (context: CombinedComponentContext<SheetContent>) -> CGSize in
let environment = context.environment[EnvironmentType.self]
let component = context.component
let state = context.state
@ -160,9 +160,14 @@ private final class SheetContent: CombinedComponent {
minAmount = StarsAmount(value: minAmountValue, nanos: 0)
maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0)
case .suggestedPost:
case let .suggestedPost(mode, _, _, _):
//TODO:localize
titleString = "Suggest Terms"
switch mode {
case .sender:
titleString = "Suggest Terms"
case .admin:
titleString = "Suggest Changes"
}
amountTitle = "ENTER A PRICE IN STARS"
amountPlaceholder = "Price"
@ -321,6 +326,13 @@ private final class SheetContent: CombinedComponent {
text: .plain(amountInfoString),
maximumNumberOfLines: 0
))
case .admin:
//TODO:localize
let amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("Choose how many Stars you charge for the message.", attributes: amountMarkdownAttributes, textAlignment: .natural))
amountFooter = AnyComponent(MultilineTextComponent(
text: .plain(amountInfoString),
maximumNumberOfLines: 0
))
}
default:
amountFooter = nil
@ -380,11 +392,19 @@ private final class SheetContent: CombinedComponent {
.position(CGPoint(x: context.availableSize.width - amountAdditionalLabel.size.width / 2.0 - sideInset - 16.0, y: contentSize.height - amountAdditionalLabel.size.height / 2.0)))
}
if case .suggestedPost = component.mode {
if case let .suggestedPost(mode, _, _, _) = component.mode {
contentSize.height += 24.0
//TODO:localize
let timestampFooterString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("Select the date and time you want your message to be published.", attributes: amountMarkdownAttributes, textAlignment: .natural))
let footerString: String
switch mode {
case .sender:
footerString = "Select the date and time you want your message to be published."
case .admin:
footerString = "Select the date and time you want to publish the message."
}
let timestampFooterString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(footerString, attributes: amountMarkdownAttributes, textAlignment: .natural))
let timestampFooter = AnyComponent(MultilineTextComponent(
text: .plain(timestampFooterString),
maximumNumberOfLines: 0
@ -443,15 +463,9 @@ private final class SheetContent: CombinedComponent {
return
}
let component = state.component
guard case let .suggestedPost(mode, _, _, _) = component.mode else {
return
}
guard case let .sender(channel) = mode else {
return
}
let theme = environment.theme
let controller = ChatScheduleTimeController(context: state.context, updatedPresentationData: (state.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), state.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), peerId: channel.id, mode: .suggestPost, style: .default, currentTime: state.timestamp, minimalTime: nil, dismissByTapOutside: true, completion: { [weak state] time in
let controller = ChatScheduleTimeController(context: state.context, updatedPresentationData: (state.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), state.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: .suggestPost(needsTime: false), style: .default, currentTime: state.timestamp, minimalTime: nil, dismissByTapOutside: true, completion: { [weak state] time in
guard let state else {
return
}
@ -498,6 +512,8 @@ private final class SheetContent: CombinedComponent {
} else {
buttonString = "Offer"
}
case .admin:
buttonString = "Update Terms"
}
} else if let amount = state.amount {
buttonString = "\(environment.strings.Stars_Withdraw_Withdraw) # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))"
@ -770,6 +786,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
public enum Mode {
public enum SuggestedPostMode {
case sender(channel: EnginePeer)
case admin
}
case withdraw(StarsRevenueStats, completion: (Int64) -> Void)

View File

@ -466,6 +466,7 @@ final class StoryItemSetContainerSendMessage {
guard let peerId = focusedItem.peerId else {
return
}
let _ = peerId
let controller = component.controller() as? StoryContainerScreen
var sendWhenOnlineAvailable = false
@ -473,7 +474,7 @@ final class StoryItemSetContainerSendMessage {
sendWhenOnlineAvailable = true
}
let timeController = ChatScheduleTimeController(context: component.context, updatedPresentationData: nil, peerId: peerId, mode: .scheduledMessages(sendWhenOnlineAvailable: sendWhenOnlineAvailable), style: .media, currentTime: nil, minimalTime: nil, dismissByTapOutside: true, completion: { [weak self, weak view] time in
let timeController = ChatScheduleTimeController(context: component.context, updatedPresentationData: nil, mode: .scheduledMessages(sendWhenOnlineAvailable: sendWhenOnlineAvailable), style: .media, currentTime: nil, minimalTime: nil, dismissByTapOutside: true, completion: { [weak self, weak view] time in
guard let self, let view else {
return
}
@ -2607,7 +2608,7 @@ final class StoryItemSetContainerSendMessage {
mode = .scheduledMessages(sendWhenOnlineAvailable: sendWhenOnlineAvailable)
}
let theme = component.theme
let controller = ChatScheduleTimeController(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), peerId: peer.id, mode: mode, style: style, currentTime: selectedTime, minimalTime: nil, dismissByTapOutside: dismissByTapOutside, completion: { time in
let controller = ChatScheduleTimeController(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: mode, style: style, currentTime: selectedTime, minimalTime: nil, dismissByTapOutside: dismissByTapOutside, completion: { time in
completion(time)
})
view.endEditing(true)

View File

@ -137,6 +137,7 @@ import ChatMessagePaymentAlertController
import TelegramCallsUI
import QuickShareScreen
import PostSuggestionsSettingsScreen
import PromptUI
public final class ChatControllerOverlayPresentationData {
public let expandData: (ASDisplayNode?, () -> Void)
@ -2331,16 +2332,49 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if message.effectivelyIncoming(strongSelf.context.account.peerId) {
switch buttonType {
case 0:
let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .reject(comment: nil)).startStandalone()
//TODO:localize
let promptController = promptController(sharedContext: strongSelf.context.sharedContext, updatedPresentationData: strongSelf.updatedPresentationData, text: "Comment", titleFont: .bold, value: "", placeholder: "Optional", characterLimit: 4096, apply: { value in
guard let self else {
return
}
if let value {
let _ = self.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .reject(comment: value.isEmpty ? nil : value)).startStandalone()
}
})
strongSelf.present(promptController, in: .window(.root))
case 1:
var timestamp: Int32?
if attribute.timestamp == nil {
let controller = ChatScheduleTimeController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .suggestPost(needsTime: true), style: .default, currentTime: nil, minimalTime: nil, dismissByTapOutside: true, completion: { [weak strongSelf] time in
guard let strongSelf else {
return
}
if time != 0 {
let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: time)).startStandalone()
}
})
strongSelf.view.endEditing(true)
strongSelf.present(controller, in: .window(.root))
timestamp = Int32(Date().timeIntervalSince1970) + 1 * 60 * 60
} else {
let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: timestamp)).startStandalone()
}
let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: timestamp)).startStandalone()
case 2:
//suggest changes
break
strongSelf.push(strongSelf.context.sharedContext.makeStarsWithdrawalScreen(
context: strongSelf.context,
subject: .postSuggestionModification(
current: StarsAmount(value: attribute.amount, nanos: 0),
timestamp: attribute.timestamp,
completion: { [weak strongSelf] price, timestamp in
guard let strongSelf else {
return
}
let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .proposeChanges(amount: price, timestamp: timestamp)).startStandalone()
}
)
))
default:
break
}
@ -9525,7 +9559,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} else {
mode = .scheduledMessages(sendWhenOnlineAvailable: sendWhenOnlineAvailable)
}
let controller = ChatScheduleTimeController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, mode: mode, style: style, currentTime: selectedTime, minimalTime: strongSelf.presentationInterfaceState.slowmodeState?.timeout, dismissByTapOutside: dismissByTapOutside, completion: { time in
let controller = ChatScheduleTimeController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: mode, style: style, currentTime: selectedTime, minimalTime: strongSelf.presentationInterfaceState.slowmodeState?.timeout, dismissByTapOutside: dismissByTapOutside, completion: { time in
completion(time)
})
strongSelf.chatDisplayNode.dismissInput()

View File

@ -233,7 +233,7 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
}
}
if isTextEmpty, let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let mainChannel = chatPresentationInterfaceState.renderedPeer?.chatOrMonoforumMainPeer as? TelegramChannel, !mainChannel.hasPermission(.sendSomething) {
if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let mainChannel = chatPresentationInterfaceState.renderedPeer?.chatOrMonoforumMainPeer as? TelegramChannel, !mainChannel.hasPermission(.sendSomething) {
if chatPresentationInterfaceState.interfaceState.postSuggestionState == nil {
accessoryItems.append(.suggestPost)
}

View File

@ -3731,6 +3731,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mode = .paidMessages(current: current.value, minValue: minValue.value, fractionAfterCommission: fractionAfterCommission, kind: kind, completion: completion)
case let .postSuggestion(channel, current, timestamp, completion):
mode = .suggestedPost(mode: .sender(channel: channel), price: current.value, timestamp: timestamp, completion: completion)
case let .postSuggestionModification(current, timestamp, completion):
mode = .suggestedPost(mode: .admin, price: current.value, timestamp: timestamp, completion: completion)
}
return StarsWithdrawScreen(context: context, mode: mode)
}