diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 8dbdc1b27c..4c40bb195f 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1034,6 +1034,7 @@ public enum StarsWithdrawalScreenSubject { case withdraw(completion: (Int64) -> Void) case enterAmount(current: StarsAmount, minValue: StarsAmount, fractionAfterCommission: Int, kind: PaidMessageKind, completion: (Int64) -> Void) case postSuggestion(channel: EnginePeer, current: StarsAmount, timestamp: Int32?, completion: (Int64, Int32?) -> Void) + case postSuggestionModification(current: StarsAmount, timestamp: Int32?, completion: (Int64, Int32?) -> Void) } public protocol SharedAccountContext: AnyObject { diff --git a/submodules/PromptUI/Sources/PromptController.swift b/submodules/PromptUI/Sources/PromptController.swift index 46e58a30ab..06dee32735 100644 --- a/submodules/PromptUI/Sources/PromptController.swift +++ b/submodules/PromptUI/Sources/PromptController.swift @@ -189,7 +189,7 @@ private final class PromptAlertContentNode: AlertContentNode { return self.isUserInteractionEnabled } - init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, titleFont: PromptControllerTitleFont, value: String?, characterLimit: Int) { + init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, titleFont: PromptControllerTitleFont, value: String?, placeholder: String?, characterLimit: Int) { self.strings = strings self.text = text self.titleFont = titleFont @@ -197,7 +197,7 @@ private final class PromptAlertContentNode: AlertContentNode { self.textNode = ASTextNode() self.textNode.maximumNumberOfLines = 2 - self.inputFieldNode = PromptInputFieldNode(theme: ptheme, placeholder: "", characterLimit: characterLimit) + self.inputFieldNode = PromptInputFieldNode(theme: ptheme, placeholder: placeholder ?? "", characterLimit: characterLimit) self.inputFieldNode.text = value ?? "" self.actionNodesSeparator = ASDisplayNode() @@ -407,7 +407,7 @@ public enum PromptControllerTitleFont { case bold } -public func promptController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, text: String, titleFont: PromptControllerTitleFont = .regular, value: String?, characterLimit: Int = 1000, apply: @escaping (String?) -> Void) -> AlertController { +public func promptController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, text: String, titleFont: PromptControllerTitleFont = .regular, value: String?, placeholder: String? = nil, characterLimit: Int = 1000, apply: @escaping (String?) -> Void) -> AlertController { let presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 } var dismissImpl: ((Bool) -> Void)? @@ -421,8 +421,9 @@ public func promptController(sharedContext: SharedAccountContext, updatedPresent applyImpl?() })] - let contentNode = PromptAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, titleFont: titleFont, value: value, characterLimit: characterLimit) + let contentNode = PromptAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, titleFont: titleFont, value: value, placeholder: placeholder, characterLimit: characterLimit) contentNode.complete = { + dismissImpl?(true) applyImpl?() } applyImpl = { [weak contentNode] in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index b1e1a49b2d..6303f27d11 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -1591,6 +1591,7 @@ public extension TelegramEngine { public enum MonoforumSuggestedPostAction { case approve(timestamp: Int32?) case reject(comment: String?) + case proposeChanges(amount: Int64, timestamp: Int32?) } public func monoforumPerformSuggestedPostAction(id: EngineMessage.Id, action: MonoforumSuggestedPostAction) -> Signal { @@ -1601,6 +1602,34 @@ public extension TelegramEngine { func _internal_monoforumPerformSuggestedPostAction(account: Account, id: EngineMessage.Id, action: TelegramEngine.Messages.MonoforumSuggestedPostAction) -> Signal { return account.postbox.transaction { transaction -> Api.InputPeer? in + if case let .proposeChanges(amount, timestamp) = action, let message = transaction.getMessage(id) { + var attributes: [MessageAttribute] = [] + attributes.append(SuggestedPostMessageAttribute( + amount: amount, + timestamp: timestamp, + state: nil + )) + + var mediaReference: AnyMediaReference? + if let media = message.media.first { + mediaReference = .message(message: MessageReference(message), media: media) + } + + let enqueueMessage: EnqueueMessage = .message( + text: message.text, + attributes: attributes, + inlineStickers: [:], + mediaReference: mediaReference, + threadId: message.threadId, + replyToMessageId: EngineMessageReplySubject(messageId: message.id, quote: nil), + replyToStoryId: nil, + localGroupingKey: nil, + correlationId: nil, + bubbleUpEmojiOrStickersets: [] + ) + let _ = enqueueMessages(transaction: transaction, account: account, peerId: id.peerId, messages: [(true, enqueueMessage)]) + } + return transaction.getPeer(id.peerId).flatMap(apiInputPeer) } |> mapToSignal { inputPeer -> Signal in @@ -1626,6 +1655,8 @@ func _internal_monoforumPerformSuggestedPostAction(account: Account, id: EngineM if rejectComment != nil { flags |= 1 << 2 } + case .proposeChanges: + flags |= 1 << 1 } return account.network.request(Api.functions.messages.toggleSuggestedPostApproval(flags: flags, peer: inputPeer, msgId: id.id, scheduleDate: timestamp, rejectComment: rejectComment)) |> map(Optional.init) diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 1272dc5406..029e11e6f1 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1390,18 +1390,35 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } let string: String - switch status { - case .approved: - if messageText.isEmpty { - string = "Your message was approved" - } else { - string = "Your message \"\(messageText)\" was approved" + if !message.flags.contains(.Incoming) { + switch status { + case .approved: + if messageText.isEmpty { + string = "The message was approved" + } else { + string = "The message \"\(messageText)\" was approved" + } + case .rejected: + if messageText.isEmpty { + string = "The message was declined" + } else { + string = "The message \"\(messageText)\" was declined" + } } - case .rejected: - if messageText.isEmpty { - string = "Your message was rejected" - } else { - string = "Your message \"\(messageText)\" was rejected" + } else { + switch status { + case .approved: + if messageText.isEmpty { + string = "Your message was approved" + } else { + string = "Your message \"\(messageText)\" was approved" + } + case .rejected: + if messageText.isEmpty { + string = "Your message was declined" + } else { + string = "Your message \"\(messageText)\" was declined" + } } } attributedString = NSAttributedString(string: string, font: titleFont, textColor: primaryTextColor) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift index dabd04bc95..1862952975 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift @@ -231,22 +231,27 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { //TODO:localize let timeString = humanReadableStringForTimestamp(strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat, timestamp: timestamp ?? 0, alwaysShowTime: true, allowYesterday: false, format: HumanReadableStringFormat( dateFormatString: { value in - return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_Date(value).string, ranges: []) + return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_Date(value).string.lowercased(), ranges: []) }, tomorrowFormatString: { value in - return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TomorrowAt(value).string, ranges: []) + return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TomorrowAt(value).string.lowercased(), ranges: []) }, todayFormatString: { value in - return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TodayAt(value).string, ranges: []) + return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TodayAt(value).string.lowercased(), ranges: []) }, yesterdayFormatString: { value in - return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TodayAt(value).string, ranges: []) + return PresentationStrings.FormattedString(string: item.presentationData.strings.SuggestPost_SetTimeFormat_TodayAt(value).string.lowercased(), ranges: []) } )).string let amountString = amount == 1 ? "\(amount) Star" : "\(amount) Stars" - let rawString = "šŸ“… Your post will be automatically published on **\(channelName)** **\(timeString)**.\n\nšŸ’° You have been charged \(amountString).\n\nāŒ› **\(channelName)** will receive your Stars once the post has been live for 24 hours.\n\nšŸ”„ If **\(channelName)** removes the post before it has been live for 24 hours, your Stars will be refunded." + let rawString: String + if !item.message.effectivelyIncoming(item.context.account.peerId) { + rawString = "šŸ“… The post will be automatically published on **\(channelName)** **\(timeString)**.\n\nšŸ’° The user have been charged \(amountString).\n\nāŒ› **\(channelName)** will receive the Stars once the post has been live for 24 hours.\n\nšŸ”„ If your remove the post before it has been live for 24 hours, the user's Stars will be refunded." + } else { + rawString = "šŸ“… Your post will be automatically published on **\(channelName)** **\(timeString)**.\n\nšŸ’° You have been charged \(amountString).\n\nāŒ› **\(channelName)** will receive your Stars once the post has been live for 24 hours.\n\nšŸ”„ If **\(channelName)** removes the post before it has been live for 24 hours, your Stars will be refunded." + } updatedAttributedString = parseMarkdownIntoAttributedString(rawString, attributes: MarkdownAttributes( body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: primaryTextColor), @@ -257,15 +262,28 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { )) case let .rejected(reason, comment): let rawString: String - switch reason { - case .generic: - if let comment { - rawString = "**\(channelName)** declined your post with the following comment:\n\n" + comment - } else { - rawString = "**\(channelName)** declined your post." + if !item.message.effectivelyIncoming(item.context.account.peerId) { + switch reason { + case .generic: + if let comment { + rawString = "You declined the post with the following comment:\n\n" + comment + } else { + rawString = "You declined the post." + } + case .lowBalance: + rawString = "**\(channelName)** was unable to post the message, because the user did not have enough Stars." + } + } else { + switch reason { + case .generic: + if let comment { + rawString = "**\(channelName)** declined your post with the following comment:\n\n" + comment + } else { + rawString = "**\(channelName)** declined your post." + } + case .lowBalance: + rawString = "**\(channelName)** was unable to post your message, because you did not have enough Stars." } - case .lowBalance: - rawString = "**\(channelName)** was unable to post your message, because you did not have enough Stars." } updatedAttributedString = parseMarkdownIntoAttributedString(rawString, attributes: MarkdownAttributes( body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor), @@ -344,7 +362,7 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { backgroundSize.height += imageSize.height + 10.0 } - let titleSpacing: CGFloat = 18.0 + let titleSpacing: CGFloat = 14.0 var contentInsets = UIEdgeInsets() var contentOuterInsets = UIEdgeInsets() @@ -353,8 +371,8 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { backgroundSize.width = max(backgroundSize.width, titleLayoutAndApply.0.size.width) backgroundSize.height += titleSpacing + titleLayoutAndApply.0.size.height - contentInsets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0) - contentOuterInsets = UIEdgeInsets(top: 8.0, left: 0.0, bottom: 8.0, right: 0.0) + contentInsets = UIEdgeInsets(top: 12.0, left: 16.0, bottom: 12.0, right: 16.0) + contentOuterInsets = UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0) backgroundSize.width += contentInsets.left + contentInsets.right backgroundSize.height += contentInsets.top + contentInsets.bottom diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 26ae6cf18f..a47abf5b62 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -2797,7 +2797,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI actionButtonsFinalize = buttonsLayout lastNodeTopPosition = .None(.Both) - } else if incoming, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.isMonoForum, item.chatLocation.threadId != nil, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = item.message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething), let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil { + } else if incoming, /*let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = item.message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething),*/ let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil { //TODO:localize var buttonDecline: UInt8 = 0 var buttonApprove: UInt8 = 1 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/BUILD index f9e2a62062..703390463f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/BUILD @@ -22,6 +22,7 @@ swift_library( "//submodules/WallpaperBackgroundNode", "//submodules/TelegramUI/Components/Chat/ChatMessageItem", "//submodules/TelegramStringFormatting", + "//submodules/Markdown", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift index 0f19af1025..791abdd7e0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift @@ -12,6 +12,7 @@ import AccountContext import WallpaperBackgroundNode import ChatMessageItem import TelegramStringFormatting +import Markdown public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode { private var titleNode: TextNode? @@ -84,10 +85,23 @@ public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode { if !item.message.effectivelyIncoming(item.context.account.peerId) { titleText = "You suggest to post\nthis message." } else { - titleText = "\(item.message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? " ") suggests to post\nthis message." + if item.message.author is TelegramChannel { + titleText = "**\(item.message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? " ")** suggests a new price,\ntime, and text for your message." + } else { + titleText = "**\(item.message.author.flatMap(EnginePeer.init)?.compactDisplayTitle ?? " ")** suggests to post\nthis message." + } } - let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleText, font: Font.regular(13.0), textColor: serviceColor.primaryText), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + let titleAttributedText = parseMarkdownIntoAttributedString(titleText, attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: serviceColor.primaryText), + bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: serviceColor.primaryText), + link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: serviceColor.primaryText), + linkAttribute: { url in + return ("URL", url) + } + )) + + let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let priceLabelLayout = makePriceLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Price", font: Font.regular(13.0), textColor: serviceColor.primaryText.withMultipliedAlpha(0.5)), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let timeLabelLayout = makeTimeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Time", font: Font.regular(13.0), textColor: serviceColor.primaryText.withMultipliedAlpha(0.5)), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) diff --git a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift index c6e0362642..654807dc07 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift @@ -1400,13 +1400,10 @@ private final class ChatSendStarsScreenComponent: Component { guard let environment = self.environment else { return } - guard case let .suggestPost(suggestPostData) = component.initialData.subjectInitialData else { - return - } - let mode: ChatScheduleTimeControllerMode = .suggestPost + let mode: ChatScheduleTimeControllerMode = .suggestPost(needsTime: false) let theme = environment.theme - let controller = ChatScheduleTimeController(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), peerId: suggestPostData.peer.id, mode: mode, style: .default, currentTime: self.currentSuggestPostTimestamp, minimalTime: nil, dismissByTapOutside: true, completion: { [weak self] time in + let controller = ChatScheduleTimeController(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: mode, style: .default, currentTime: self.currentSuggestPostTimestamp, minimalTime: nil, dismissByTapOutside: true, completion: { [weak self] time in guard let self else { return } diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeController.swift b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeController.swift index 5b8b8655f0..61271df7e7 100644 --- a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeController.swift +++ b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeController.swift @@ -11,7 +11,7 @@ import TelegramPresentationData public enum ChatScheduleTimeControllerMode { case scheduledMessages(sendWhenOnlineAvailable: Bool) case reminders - case suggestPost + case suggestPost(needsTime: Bool) } public enum ChatScheduleTimeControllerStyle { @@ -27,7 +27,6 @@ public final class ChatScheduleTimeController: ViewController { private var animatedIn = false private let context: AccountContext - private let peerId: PeerId private let mode: ChatScheduleTimeControllerMode private let style: ChatScheduleTimeControllerStyle private let currentTime: Int32? @@ -40,9 +39,8 @@ public final class ChatScheduleTimeController: ViewController { public var dismissed: () -> Void = {} - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, mode: ChatScheduleTimeControllerMode, style: ChatScheduleTimeControllerStyle, currentTime: Int32? = nil, minimalTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: ChatScheduleTimeControllerMode, style: ChatScheduleTimeControllerStyle, currentTime: Int32? = nil, minimalTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) { self.context = context - self.peerId = peerId self.mode = mode self.style = style self.currentTime = currentTime != scheduleWhenOnlineTimestamp ? currentTime : nil diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift index 5c117b39ca..84ea7d6d69 100644 --- a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift +++ b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift @@ -100,10 +100,16 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel title = self.presentationData.strings.Conversation_ScheduleMessage_Title case .reminders: title = self.presentationData.strings.Conversation_SetReminder_Title - case .suggestPost: - //TODO:localize - title = "Time" - text = "Set the date and time you want\nyour message to be published." + case let .suggestPost(needsTime): + if needsTime { + //TODO:localize + title = "Time" + text = "Set the date and time you want\nthis message to be published." + } else { + //TODO:localize + title = "Time" + text = "Set the date and time you want\nyour message to be published." + } } self.titleNode = ASTextNode() @@ -169,7 +175,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel self.contentContainerNode.addSubnode(self.doneButton) if case .scheduledMessages(true) = self.mode { self.contentContainerNode.addSubnode(self.onlineButton) - } else if case .suggestPost = self.mode { + } else if case let .suggestPost(needsTime) = self.mode, !needsTime { self.contentContainerNode.addSubnode(self.onlineButton) } @@ -425,7 +431,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel var buttonOffset: CGFloat = 0.0 if case .scheduledMessages(true) = self.mode { buttonOffset += 64.0 - } else if case .suggestPost = self.mode { + } else if case let .suggestPost(needsTime) = self.mode, !needsTime { buttonOffset += 64.0 } diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 5e5d6007fe..84952e5109 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -50,7 +50,7 @@ private final class SheetContent: CombinedComponent { return true } - static var body: Body { + static var body: (CombinedComponentContext) -> CGSize { let closeButton = Child(Button.self) let title = Child(Text.self) let amountSection = Child(ListSectionComponent.self) @@ -61,7 +61,7 @@ private final class SheetContent: CombinedComponent { let balanceValue = Child(MultilineTextComponent.self) let balanceIcon = Child(BundleIconComponent.self) - return { context in + return { (context: CombinedComponentContext) -> CGSize in let environment = context.environment[EnvironmentType.self] let component = context.component let state = context.state @@ -160,9 +160,14 @@ private final class SheetContent: CombinedComponent { minAmount = StarsAmount(value: minAmountValue, nanos: 0) maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0) - case .suggestedPost: + case let .suggestedPost(mode, _, _, _): //TODO:localize - titleString = "Suggest Terms" + switch mode { + case .sender: + titleString = "Suggest Terms" + case .admin: + titleString = "Suggest Changes" + } amountTitle = "ENTER A PRICE IN STARS" amountPlaceholder = "Price" @@ -321,6 +326,13 @@ private final class SheetContent: CombinedComponent { text: .plain(amountInfoString), maximumNumberOfLines: 0 )) + case .admin: + //TODO:localize + let amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("Choose how many Stars you charge for the message.", attributes: amountMarkdownAttributes, textAlignment: .natural)) + amountFooter = AnyComponent(MultilineTextComponent( + text: .plain(amountInfoString), + maximumNumberOfLines: 0 + )) } default: amountFooter = nil @@ -380,11 +392,19 @@ private final class SheetContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width - amountAdditionalLabel.size.width / 2.0 - sideInset - 16.0, y: contentSize.height - amountAdditionalLabel.size.height / 2.0))) } - if case .suggestedPost = component.mode { + if case let .suggestedPost(mode, _, _, _) = component.mode { contentSize.height += 24.0 //TODO:localize - let timestampFooterString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("Select the date and time you want your message to be published.", attributes: amountMarkdownAttributes, textAlignment: .natural)) + let footerString: String + switch mode { + case .sender: + footerString = "Select the date and time you want your message to be published." + case .admin: + footerString = "Select the date and time you want to publish the message." + } + + let timestampFooterString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(footerString, attributes: amountMarkdownAttributes, textAlignment: .natural)) let timestampFooter = AnyComponent(MultilineTextComponent( text: .plain(timestampFooterString), maximumNumberOfLines: 0 @@ -443,15 +463,9 @@ private final class SheetContent: CombinedComponent { return } let component = state.component - guard case let .suggestedPost(mode, _, _, _) = component.mode else { - return - } - guard case let .sender(channel) = mode else { - return - } let theme = environment.theme - let controller = ChatScheduleTimeController(context: state.context, updatedPresentationData: (state.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), state.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), peerId: channel.id, mode: .suggestPost, style: .default, currentTime: state.timestamp, minimalTime: nil, dismissByTapOutside: true, completion: { [weak state] time in + let controller = ChatScheduleTimeController(context: state.context, updatedPresentationData: (state.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), state.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: .suggestPost(needsTime: false), style: .default, currentTime: state.timestamp, minimalTime: nil, dismissByTapOutside: true, completion: { [weak state] time in guard let state else { return } @@ -498,6 +512,8 @@ private final class SheetContent: CombinedComponent { } else { buttonString = "Offer" } + case .admin: + buttonString = "Update Terms" } } else if let amount = state.amount { buttonString = "\(environment.strings.Stars_Withdraw_Withdraw) # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))" @@ -770,6 +786,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer { public enum Mode { public enum SuggestedPostMode { case sender(channel: EnginePeer) + case admin } case withdraw(StarsRevenueStats, completion: (Int64) -> Void) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 14f1e8d9f2..4b192ed122 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -466,6 +466,7 @@ final class StoryItemSetContainerSendMessage { guard let peerId = focusedItem.peerId else { return } + let _ = peerId let controller = component.controller() as? StoryContainerScreen var sendWhenOnlineAvailable = false @@ -473,7 +474,7 @@ final class StoryItemSetContainerSendMessage { sendWhenOnlineAvailable = true } - let timeController = ChatScheduleTimeController(context: component.context, updatedPresentationData: nil, peerId: peerId, mode: .scheduledMessages(sendWhenOnlineAvailable: sendWhenOnlineAvailable), style: .media, currentTime: nil, minimalTime: nil, dismissByTapOutside: true, completion: { [weak self, weak view] time in + let timeController = ChatScheduleTimeController(context: component.context, updatedPresentationData: nil, mode: .scheduledMessages(sendWhenOnlineAvailable: sendWhenOnlineAvailable), style: .media, currentTime: nil, minimalTime: nil, dismissByTapOutside: true, completion: { [weak self, weak view] time in guard let self, let view else { return } @@ -2607,7 +2608,7 @@ final class StoryItemSetContainerSendMessage { mode = .scheduledMessages(sendWhenOnlineAvailable: sendWhenOnlineAvailable) } let theme = component.theme - let controller = ChatScheduleTimeController(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), peerId: peer.id, mode: mode, style: style, currentTime: selectedTime, minimalTime: nil, dismissByTapOutside: dismissByTapOutside, completion: { time in + let controller = ChatScheduleTimeController(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: mode, style: style, currentTime: selectedTime, minimalTime: nil, dismissByTapOutside: dismissByTapOutside, completion: { time in completion(time) }) view.endEditing(true) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index b1b10e9cf3..3ca0ebb065 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -137,6 +137,7 @@ import ChatMessagePaymentAlertController import TelegramCallsUI import QuickShareScreen import PostSuggestionsSettingsScreen +import PromptUI public final class ChatControllerOverlayPresentationData { public let expandData: (ASDisplayNode?, () -> Void) @@ -2331,16 +2332,49 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if message.effectivelyIncoming(strongSelf.context.account.peerId) { switch buttonType { case 0: - let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .reject(comment: nil)).startStandalone() + //TODO:localize + let promptController = promptController(sharedContext: strongSelf.context.sharedContext, updatedPresentationData: strongSelf.updatedPresentationData, text: "Comment", titleFont: .bold, value: "", placeholder: "Optional", characterLimit: 4096, apply: { value in + guard let self else { + return + } + if let value { + let _ = self.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .reject(comment: value.isEmpty ? nil : value)).startStandalone() + } + }) + strongSelf.present(promptController, in: .window(.root)) case 1: var timestamp: Int32? if attribute.timestamp == nil { + let controller = ChatScheduleTimeController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .suggestPost(needsTime: true), style: .default, currentTime: nil, minimalTime: nil, dismissByTapOutside: true, completion: { [weak strongSelf] time in + guard let strongSelf else { + return + } + if time != 0 { + let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: time)).startStandalone() + } + }) + strongSelf.view.endEditing(true) + strongSelf.present(controller, in: .window(.root)) + timestamp = Int32(Date().timeIntervalSince1970) + 1 * 60 * 60 + } else { + let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: timestamp)).startStandalone() } - let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: timestamp)).startStandalone() case 2: - //suggest changes - break + strongSelf.push(strongSelf.context.sharedContext.makeStarsWithdrawalScreen( + context: strongSelf.context, + subject: .postSuggestionModification( + current: StarsAmount(value: attribute.amount, nanos: 0), + timestamp: attribute.timestamp, + completion: { [weak strongSelf] price, timestamp in + guard let strongSelf else { + return + } + + let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .proposeChanges(amount: price, timestamp: timestamp)).startStandalone() + } + ) + )) default: break } @@ -9525,7 +9559,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else { mode = .scheduledMessages(sendWhenOnlineAvailable: sendWhenOnlineAvailable) } - let controller = ChatScheduleTimeController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: peerId, mode: mode, style: style, currentTime: selectedTime, minimalTime: strongSelf.presentationInterfaceState.slowmodeState?.timeout, dismissByTapOutside: dismissByTapOutside, completion: { time in + let controller = ChatScheduleTimeController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: mode, style: style, currentTime: selectedTime, minimalTime: strongSelf.presentationInterfaceState.slowmodeState?.timeout, dismissByTapOutside: dismissByTapOutside, completion: { time in completion(time) }) strongSelf.chatDisplayNode.dismissInput() diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift index 7c25066142..fb27b9fbed 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift @@ -233,7 +233,7 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte } } - if isTextEmpty, let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let mainChannel = chatPresentationInterfaceState.renderedPeer?.chatOrMonoforumMainPeer as? TelegramChannel, !mainChannel.hasPermission(.sendSomething) { + if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let mainChannel = chatPresentationInterfaceState.renderedPeer?.chatOrMonoforumMainPeer as? TelegramChannel, !mainChannel.hasPermission(.sendSomething) { if chatPresentationInterfaceState.interfaceState.postSuggestionState == nil { accessoryItems.append(.suggestPost) } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 5ebb3a0069..37b6b1ad6f 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -3731,6 +3731,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mode = .paidMessages(current: current.value, minValue: minValue.value, fractionAfterCommission: fractionAfterCommission, kind: kind, completion: completion) case let .postSuggestion(channel, current, timestamp, completion): mode = .suggestedPost(mode: .sender(channel: channel), price: current.value, timestamp: timestamp, completion: completion) + case let .postSuggestionModification(current, timestamp, completion): + mode = .suggestedPost(mode: .admin, price: current.value, timestamp: timestamp, completion: completion) } return StarsWithdrawScreen(context: context, mode: mode) }