Monoforums

This commit is contained in:
Isaac 2025-05-27 00:56:39 +08:00
parent 60f2b98ee8
commit c984cb956b
12 changed files with 83 additions and 53 deletions

View File

@ -14013,9 +14013,17 @@ Sorry for the inconvenience.";
"Notification.PaidMessagePriceChanged.Stars_1" = "%@ Star";
"Notification.PaidMessagePriceChanged.Stars_any" = "%@ Stars";
"Notification.PaidMessagePriceChanged" = "%1$@ changed price to %2$@ per message";
"Notification.PaidMessagePriceChangedAndEnabledChannelMessage" = "%1$@ changed price to %2$@ per message, and enabled messaging this channel";
"Notification.PaidMessagePriceChangedYou" = "You changed price to %1$@ per message";
"Notification.PaidMessagePriceChangedAndEnabledChannelMessageYou" = "You changed price to %1$@ per message, and enabled messaging this channel";
"Notification.ChannelMessagePriceChanged_1" = "{name} set the private message price to %d star";
"Notification.ChannelMessagePriceChanged_any" = "{name} set the private message price to %d stars";
"Notification.ChannelMessagePriceZeroChanged" = "%@ now accepts private messages";
"Notification.ChannelMessagePriceChangedAndEnabledChannelMessage_1" = "{name} now accepts private messages for %d star";
"Notification.ChannelMessagePriceChangedAndEnabledChannelMessage_any" = "{name} now accepts private messages for %d stars";
"Notification.ChannelMessagePriceZeroChangedAndEnabledChannelMessage" = "%@ now accepts private messages";
"Notification.ChannelMessageDisabled" = "%@ no longer accepts private messages";
"Premium.MaxExpiringStoriesTextNumberFormat_1" = "**%d** story";
"Premium.MaxExpiringStoriesTextNumberFormat_any" = "**%d** stories";
@ -14368,3 +14376,9 @@ Sorry for the inconvenience.";
"PeerInfo.OptionTopics.Enabled" = "Enabled";
"PeerInfo.OptionTopics.Disabled" = "Disabled";
"ChannelMessages.Title" = "Allow Channel Messages";
"ChannelMessages.Info" = "Allow users to send messages to your channel, with the option to charge a fee for each message.";
"ChannelMessages.SwitchTitle" = "Allow Channel Messages";
"ChannelMessages.PriceSectionTitle" = "PRICE FOR EACH MESSAGE";
"ChannelMessages.PriceSectionFooter" = "Charge users for the ability to suggest one post for your channel. You're not required to publish any suggestions by charging this. You'll receive 85% of the selected fee for each incoming suggestion.";

View File

@ -1297,7 +1297,11 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
if let data = view.cachedData as? CachedUserData {
return data.sendPaidMessageStars
} else if let channel = peerViewMainPeer(view) as? TelegramChannel {
return channel.sendPaidMessageStars
if channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = view.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething) {
return nil
} else {
return channel.sendPaidMessageStars
}
} else {
return nil
}

View File

@ -949,7 +949,9 @@ public final class PendingMessageManager {
var monoforumPeerId: Api.InputPeer?
if let threadId = messages[0].0.threadId {
if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) {
monoforumPeerId = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer)
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = transaction.getPeer(linkedMonoforumId) as? TelegramChannel, mainChannel.hasPermission(.sendSomething) {
monoforumPeerId = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer)
}
} else {
flags |= Int32(1 << 9)
topMsgId = Int32(clamping: threadId)
@ -1043,7 +1045,9 @@ public final class PendingMessageManager {
var monoforumPeerId: Api.InputPeer?
if let threadId = messages[0].0.threadId {
if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) {
monoforumPeerId = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer)
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = transaction.getPeer(linkedMonoforumId) as? TelegramChannel, mainChannel.hasPermission(.sendSomething) {
monoforumPeerId = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer)
}
} else {
flags |= Int32(1 << 9)
topMsgId = Int32(clamping: threadId)
@ -1352,7 +1356,9 @@ public final class PendingMessageManager {
var monoforumPeerId: Api.InputPeer?
if let threadId = message.threadId {
if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) {
monoforumPeerId = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer)
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = transaction.getPeer(linkedMonoforumId) as? TelegramChannel, mainChannel.hasPermission(.sendSomething) {
monoforumPeerId = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer)
}
} else {
topMsgId = Int32(clamping: threadId)
}
@ -1615,7 +1621,9 @@ public final class PendingMessageManager {
var monoforumPeerId: Api.InputPeer?
if let threadId = message.threadId {
if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) {
monoforumPeerId = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer)
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = transaction.getPeer(linkedMonoforumId) as? TelegramChannel, mainChannel.hasPermission(.sendSomething) {
monoforumPeerId = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer)
}
} else {
flags |= Int32(1 << 9)
topMsgId = Int32(clamping: threadId)

View File

@ -1262,21 +1262,28 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
let starsString = strings.Notification_PaidMessagePriceChanged_Stars(Int32(stars))
if message.author?.id == accountPeerId {
let resultString: PresentationStrings.FormattedString
if broadcastMessagesAllowed {
resultString = strings.Notification_PaidMessagePriceChangedAndEnabledChannelMessageYou(starsString)
} else {
resultString = strings.Notification_PaidMessagePriceChangedYou(starsString)
}
resultString = strings.Notification_PaidMessagePriceChangedYou(starsString)
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
} else {
let peerName = message.author?.compactDisplayTitle ?? ""
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])
attributes[1] = boldAttributes
let resultString: PresentationStrings.FormattedString
if broadcastMessagesAllowed {
resultString = strings.Notification_PaidMessagePriceChangedAndEnabledChannelMessage(peerName, starsString)
if stars == 0 {
resultString = strings.Notification_ChannelMessagePriceZeroChanged(peerName)
} else {
var rawString = strings.Notification_ChannelMessagePriceChanged(Int32(stars))
rawString = rawString.replacingOccurrences(of: "{name}", with: peerName)
resultString = PresentationStrings.FormattedString(string: rawString, ranges: [])
}
} else {
resultString = strings.Notification_PaidMessagePriceChanged(peerName, starsString)
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
resultString = strings.Notification_ChannelMessageDisabled(peerName)
} else {
resultString = strings.Notification_PaidMessagePriceChanged(peerName, starsString)
}
}
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: attributes)
}

View File

@ -382,7 +382,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = presentationData
//TODO:localize
let text: String = "Tap here to suggest a message"
let text: String = "Tap here to send a message"
let tooltipController = TooltipScreen(
account: context.account,

View File

@ -1597,7 +1597,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
}
if isMonoForum {
if case .replyThread = item.chatLocation {
if case let .replyThread(replyThreadMessage) = item.chatLocation, firstMessage.effectivelyIncoming(item.context.account.peerId), item.effectiveAuthorId == PeerId(replyThreadMessage.threadId) {
displayAuthorInfo = false
}
}

View File

@ -1587,7 +1587,7 @@ public final class ChatSideTopicsPanel: Component {
case .side:
selectedLineFrame = CGRect(origin: CGPoint(x: 0.0, y: selectedItemFrame.minY), size: CGSize(width: 4.0, height: selectedItemFrame.height))
case .top:
selectedLineFrame = CGRect(origin: CGPoint(x: selectedItemFrame.minX, y: listView.frame.maxY - 4.0), size: CGSize(width: selectedItemFrame.width, height: 4.0))
selectedLineFrame = CGRect(origin: CGPoint(x: selectedItemFrame.minX, y: listView.frame.maxY - 3.0), size: CGSize(width: selectedItemFrame.width, height: 3.0))
}
self.selectedLineContainer.updatePosition(position: selectedLineFrame.origin, transition: lineTransition)
@ -1729,10 +1729,11 @@ public final class ChatSideTopicsPanel: Component {
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - size.height, y: 0.0), size: CGSize(width: size.height, height: size.height)))
})?.stretchableImage(withLeftCapWidth: 1, topCapHeight: 4)
case .top:
self.selectedLineView.image = generateImage(CGSize(width: 4.0, height: 2.0), rotatedContext: { size, context in
self.selectedLineView.image = generateImage(CGSize(width: 4.0, height: 3.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(component.theme.rootController.navigationBar.accentTextColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.width)))
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height * 2.0)), cornerRadius: 2.0).cgPath)
context.fillPath()
})?.stretchableImage(withLeftCapWidth: 2, topCapHeight: 1)
}
@ -1923,7 +1924,7 @@ public final class ChatSideTopicsPanel: Component {
}
component.updateTopicId(topicId, direction)
}
let itemContextGesture: ((ContextGesture, ContextExtractedContentContainingNode) -> Void)? = (self.isReordering && !component.isMonoforum) ? nil : { [weak self] gesture, sourceNode in
let itemContextGesture: ((ContextGesture, ContextExtractedContentContainingNode) -> Void)? = (self.isReordering || component.isMonoforum) ? nil : { [weak self] gesture, sourceNode in
guard let self, let component = self.component else {
return
}

View File

@ -2220,7 +2220,7 @@ private func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostSt
}))
//TODO:localize
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPostSuggestionsSettings, label: .text(channel.linkedMonoforumId == nil ? "Off" : "On"), additionalBadgeLabel: presentationData.strings.Settings_New, text: "Message Channel", icon: UIImage(bundleImageName: "Chat/Info/PostSuggestionsIcon"), action: {
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPostSuggestionsSettings, label: .text(channel.linkedMonoforumId == nil ? "Off" : "On"), additionalBadgeLabel: presentationData.strings.Settings_New, text: "Allow Channel Messages", icon: UIImage(bundleImageName: "Chat/Info/PostSuggestionsIcon"), action: {
interaction.editingOpenPostSuggestionsSetup()
}))
}

View File

@ -124,7 +124,7 @@ final class PostSuggestionsSettingsScreenComponent: Component {
}
if component.initialPrice != currentAmount {
let _ = component.context.engine.peers.updateChannelPaidMessagesStars(peerId: peer.id, stars: currentAmount, broadcastMessagesAllowed: true).startStandalone()
let _ = component.context.engine.peers.updateChannelPaidMessagesStars(peerId: peer.id, stars: currentAmount, broadcastMessagesAllowed: currentAmount != nil).startStandalone()
}
return true
@ -200,11 +200,10 @@ final class PostSuggestionsSettingsScreenComponent: Component {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
//TODO:localize
let navigationTitleSize = self.navigationTitle.update(
transition: transition,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "Post Suggestion", font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)),
text: .plain(NSAttributedString(string: environment.strings.ChannelMessages_Title, font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)),
horizontalAlignment: .center
)),
environment: {},
@ -249,8 +248,7 @@ final class PostSuggestionsSettingsScreenComponent: Component {
contentHeight += 129.0
//TODO:localize
let subtitleString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString("Allow users to suggest posts for your channel.", attributes: MarkdownAttributes(
let subtitleString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.ChannelMessages_Info, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor),
bold: MarkdownAttributeSet(font: Font.semibold(15.0), textColor: environment.theme.list.freeTextColor),
link: MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemAccentColor),
@ -301,7 +299,7 @@ final class PostSuggestionsSettingsScreenComponent: Component {
title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Allow Post Suggestions",
string: environment.strings.ChannelMessages_SwitchTitle,
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: environment.theme.list.itemPrimaryTextColor
)),
@ -385,26 +383,6 @@ final class PostSuggestionsSettingsScreenComponent: Component {
),
params: ListViewItemLayoutParams(width: availableSize.width - sideInset * 2.0, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true)
))))
/*contentSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemSliderSelectorComponent(
theme: environment.theme,
content: .discrete(ListItemSliderSelectorComponent.Discrete(
values: sliderValueList.map { item in
return item
},
markPositions: false,
selectedIndex: max(0, min(sliderValueList.count - 1, self.starCount - 1)),
title: sliderTitle,
secondaryTitle: sliderSecondaryTitle,
selectedIndexUpdated: { [weak self] index in
guard let self else {
return
}
let index = max(0, min(sliderValueList.count, index))
self.starCount = index
self.state?.updated(transition: .immediate)
}
))
))))*/
let contentSectionSize = self.contentSection.update(
transition: transition,
@ -412,7 +390,7 @@ final class PostSuggestionsSettingsScreenComponent: Component {
theme: environment.theme,
header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "PRICE FOR EACH SUGGESTION",
string: environment.strings.ChannelMessages_PriceSectionTitle,
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)),
@ -420,7 +398,7 @@ final class PostSuggestionsSettingsScreenComponent: Component {
)),
footer: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Charge users for the ability to suggest one post for your channel. You're not required to publish any suggestions by charging this. You'll receive 85% of the selected fee for each incoming suggestion.",
string: environment.strings.ChannelMessages_PriceSectionFooter,
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)),

View File

@ -7835,8 +7835,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum {
attributes.removeAll(where: { $0 is SendAsMessageAttribute })
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething), let sendAsPeerId = self.presentationInterfaceState.currentSendAsPeerId {
attributes.append(SendAsMessageAttribute(peerId: sendAsPeerId))
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething) {
if let sendAsPeerId = self.presentationInterfaceState.currentSendAsPeerId {
attributes.append(SendAsMessageAttribute(peerId: sendAsPeerId))
} else {
attributes.append(SendAsMessageAttribute(peerId: linkedMonoforumId))
}
}
}
if let sendAsPeerId = self.presentationInterfaceState.currentSendAsPeerId {

View File

@ -1447,11 +1447,25 @@ extension ChatControllerImpl {
)
}
var currentSendAsPeerId: PeerId?
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData {
if peer.isMonoForum {
if let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething) {
currentSendAsPeerId = peer.linkedMonoforumId
} else {
currentSendAsPeerId = nil
}
} else {
currentSendAsPeerId = cachedData.sendAsPeerId
}
}
strongSelf.state.renderedPeer = renderedPeer
strongSelf.state.savedMessagesTopicPeer = savedMessagesPeer?.peer
strongSelf.state.hasSearchTags = hasSearchTags
strongSelf.state.hasSavedChats = hasSavedChats
strongSelf.state.hasScheduledMessages = hasScheduledMessages
strongSelf.state.currentSendAsPeerId = currentSendAsPeerId
} else {
let message = messageAndTopic.message

View File

@ -80,7 +80,7 @@ extension ChatControllerImpl {
if let value = channel.hasBannedPermission(.banSendText, ignoreDefault: canByPassRestrictions) {
banSendText = value
}
if channel.hasBannedPermission(.banSendPolls, ignoreDefault: canByPassRestrictions) != nil {
if channel.hasBannedPermission(.banSendPolls, ignoreDefault: canByPassRestrictions) != nil || channel.isMonoForum {
canSendPolls = false
}
} else if let group = peer as? TelegramGroup {
@ -764,7 +764,7 @@ extension ChatControllerImpl {
if let value = channel.hasBannedPermission(.banSendMedia) {
bannedSendMedia = value
}
if channel.hasBannedPermission(.banSendPolls) != nil {
if channel.hasBannedPermission(.banSendPolls) != nil || channel.isMonoForum {
canSendPolls = false
}
} else if let group = peer as? TelegramGroup {